I agree that the behavior of match
in Typed Racket is less useful than people coming from languages like ML or Haskell hope it might be.
[I started writing before dinner, and meanwhile @jbclements has explained, but perhaps some of this is still useful.]
To understand the behavior of your program, it might help to think about match
expanding it to code like this:
#lang typed/racket
(: foo (All [T] (-> (-> (Listof T)) Number)))
(define (foo l1)
(cond
[(null? l1)
0]
[(list? l1)
(define l2 (rest l1))
(+ 1 (foo l2))]
[else
(error "no match")]))
Typed Racket actually does note that the branches are impossible, or your program would fail to typecheck!
The type of list?
is (-> Any Boolean : (Listof Any))
, where the proposition at the end says what the typechecker can know about any value for which list?
returns #t
.
By hovering over the closing parenthesis in DrRacket, we can see what Typed Racket has conclusded the type of the expression (list? l1)
is False
.
I'll leave the room for improvement for others to discuss in more detail. It does seem like there could be some more useful warning about bottom cases, but I wouldn't be shocked if there's macro-generated code that relies on this behavior to typecheck. A pattern-matching form that does more useful things with types would certainly be nice to have, but I also know there are challenges to writing one.
The fact that match
has an implicit else
clause raising a runtime error surprises people who are used to pattern matching with an exhaustiveness checker. Interestingly, while in an untyped context match
's behavior is far preferable, cond
's implicit default [else (void)]
is more useful for catching bugs in Typed Racket.
Maybe there are improvements that could be made to the pattern-matching caveats in the guide? 8 Caveats and Limitations
I tend to use match
less in Typed Racket for these reasons. For convenient destructuring binding, you can get much of the same benefit using forms like match-define
or match-let
once you know the shape of the value you have.