I am trying to use Typed Racket with objects (class instances) and I am unable to get to type check a program where an object instance is created on demand. The program below illustrates what I am trying to do:
Basically, the-instance holds a reference to a a Hello-Class%, but I want to create the object the first time say-hello is called. The program above fails because it thinks that the-instance can still be #f when send is used:
Type Checker: send: type mismatch
expected: an object
given: (U (Instance Hello-Class%) False) in: (send the-instance say-hello)
If I don't try to create the instance, the program type-checks (but it is useless):
The problem here is that Typed Racket's occurrence typing does not refine the type of mutable variables (in this case it could; in the general case it's hard). So your program has the same type error as this one:
#lang typed/racket
(define v : (U #f Number) #f)
(define (add-to-v)
(unless v
(set! v 18))
(+ v 1))
The fix for that program is just to use a local binding to an immutable variable, like this:
(define (add-to-v)
(define v2 (or v 18))
(+ v2 1))
If you also want to do the update, you can do it this way:
(define (add-update-v!)
(unless v (set! v 18))
(define v2 v)
(when v2
(+ v2 1)))
Hi @gus-massa , your program does indeed type check, but it is not correct, since the-instance is not a class. This means that (class? the-instance) is #f and TR simply eliminates the when clause. If you change the test to (is-a? the-instance hello-class%), this will be #t, but TR will fail to type check, thinking that the-instance can still be #f. I also experimented with various test conditions for the when clause and the ones that are #t also fail to type-check.
@samth , thanks for providing a solution for this. Is this a TR limitation or there is some logic error in the program that TR is trying to point out? .
As a side note, I am not trying to create a very complicated "Hello World" program, and the example I provided is a simplified use case.
In my case, I must delay the creation of the instance, otherwise my code will end up with an infinite loop of classes creating mutual instances of themselves. I could create the object every time it is needed, but this would result in about 100k short lived objects being created. In any case, at least there will be no other threads to interfere with the object creation
The way Typed Racket works is that this program is going to be typechecked the same way as your program. However, f could be (lambda () (set! the-instance #f)), which would make the program fail.
Even more tricky is the case where there's no call to f in say-hello at all, but somewhere else in your program you do this: (thread (lambda () (let loop () (set! the-instance #f) (loop)))), then your say-hello function might nonetheless fail.
It's possible to recognize that you aren't doing these things, via some more sophisticated analysis. That's what Flow, the gradual type system for JavaScript from Facebook, does. (See their paper here: https://dl.acm.org/doi/10.1145/3133872). But since Racket programs typically feature much less mutation, Typed Racket takes the simpler approach, and thus you need to do the extra let-binding.