I'm working with hash tables in Typed Racket, and trying to use the (untyped) hash-union
function from racket/hash
. Using require/typed
to import it into my module and call it will pass the type checker and compile.
#lang typed/racket/base
(require/typed racket/hash
[hash-union (All (k v) (->* ((Immutable-HashTable k v))
(#:combine (-> v v v) #:combine/key (-> k v v v))
#:rest (HashTable k v)
(Immutable-HashTable k v)))])
(: pick-first (All (a) (-> a a a)))
(define (pick-first v1 v2) v1)
(: make-ci-table (-> String (Immutable-HashTable Char Integer)))
(define (make-ci-table pat)
(let ([patlen (string-length pat)])
((inst hash-union Char Integer)
(for/hasheqv : (Immutable-HashTable Char Integer) ([i (in-range patlen)])
(values (char-upcase (string-ref pat i)) (- patlen 1 i)))
(for/hasheqv : (Immutable-HashTable Char Integer) ([i (in-range patlen)])
(values (char-downcase (string-ref pat i)) (- patlen 1 i)))
#:combine pick-first)))
However, actually running it will cause a contract failure:
raco test: (submod "main.rkt" test)
hash/c: contract violation
expected: chaperone-contract?
given: k33
context...:
/usr/share/racket/collects/racket/contract/private/hash.rkt:61:0: hash/c
/usr/share/racket/pkgs/typed-racket-lib/typed-racket/utils/hash-contract.rkt:28:0: hash/c/check-key
/usr/share/racket/pkgs/typed-racket-lib/typed-racket/utils/hash-contract.rkt:22:0: immutable-hash/c
/usr/share/racket/collects/racket/contract/private/parametric.rkt:26:36
/usr/share/racket/collects/racket/contract/private/parametric.rkt:82:7: wrap
/usr/share/racket/collects/racket/contract/private/parametric.rkt:100:10
...
The equivalent in normal Racket runs fine. It appears to be failing in testing if a table is immutable if I'm reading that back trace correctly (Though I have no idea why a chaperone contract is involved, or why it's getting some other value). Changing the rest type to Immutable-HashTable
doesn't change anything, and the tables returned by TR's for/hasheqv
are immutable ones.
Any ideas on what's causing this and how to fix it? (Other than building the final table up with for/fold
or some other alternative approach, which is my plan if I can't resolve this issue with hash-union
).
The problem is that polymorphism in the keys of hash tables doesn't work well with the contracts that Typed Racket puts on it when you import it from untyped Racket. The error message:
hash/c: contract violation
expected: chaperone-contract?
given: k33
Mentions k33
which comes from your k
in (All (k v) .... (Immutable-HashTable k v) ....)
.
The first step to fixing this error is to use a concrete type like Any
instead of a polymorphic k
, as in (All (v) .... (Immutable-HashTable Any v) ....)
.
The next error after that might be something like:
hash-union: contract violation
expected: "hash-equal? (because the key contract is not a flat contract)"
given: '#hasheqv()
This is because hasheq
and hasheqv
don't work well with the contracts that Typed Racket puts on it. You can fix that by using hash
or hashalw
instead, as in (for/hash : (Immutable-HashTable Any Integer) ....)
.
If you run into other contract problems that you can't fix in similar ways, it's also possible to bypass the contracts that Typed Racket puts on it, by importing it unsafely, but that should be a last-resort.