James Forbes James Forbes - 2 months ago 10
TypeScript Question

Why are first class functions not type checked in typescript?

In the example below, the

Promise::then
visitor function
f
has an expectation of receiving an
Example
. If I explicitly pass in a malformed object with missing properties, I get a type error.

But if I simply pass the function
f
to
.then(f)
I get no type error, even though typescript knows the type of the
T
in
Promise<T>
is not an
Example
.

interface Example {
id: number
age: number
}

interface Promise <T> {
then <U> ( f: ( a: T) => U ) : Promise<U>
}

function f(s:Example){
return s.age
}

var p : Promise<{ id: number }>

p.then(f) // no type error (bad)

p.then(function(a){
f(a) // type error (good)
})


I'm wondering why this happens but also, techniques to mitigate permissive type checking in Typescript.

If bivariance explains this phenomenon, then why is there a type error when
f
is explicitly applied? Why are their different rules for 1st class functions vs explicit application?

Answer

Here is what I think is happening:

p.then(f) // no type error (bad)
  • T here is { id: number }
  • then() is declared to take argument of type {id: number} => U
  • f has type Example => number
  • Example is assignable to {id: number}, so f is compatible with argument type of then() due to bivariance

Let me add one more example:

p.then(function(a : Example){
    f(a)
})

It also compiles without errors, for the same reason:

  • argument for then() has type Example => number

Now, why does the following not compile?

p.then(function(a){
    f(a) // type error (good)
})
  • I guess that a is deduced to have type {id: number} (because the simplest thing to do is to take it to be T from the declaration of p)
  • so f is called with incompatible argument here
  • because {id: number} is not assignable to Example that f needs (age is missing).

Notice that the error is not about argument for then(), but about argument for f().

Now, for the second part of the question:

techniques to mitigate permissive type checking in Typescript.

If you need correct type checking, IMO Typescript is not an option as long as unsound but "usable" typechecking remains one of its explicit design goals. I've heard that Scala, Haskell and OCaml all have compilers targeting javacsript nowadays (but I have no idea how usable they are).

And the third part of the question:

Why are their different rules for 1st class functions vs explicit application?

Because in the first case the argument is a function, and in the second case (which does not compile) the argument has deduced type {id : number}, which is not a function. Bivariance is an ad-hoc rule that applies only when it needs to be decided if one function type is compatible with another function type. The first case compiles because once it sees that then() argument is OK according to the rules, it does not descend into then() implementation to check how it will actually work there. That's what makes it unsound.

Comments