Search code examples
rackettyped-racket

Using hash-union in typed racket


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).


Solution

  • 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.