2
votes

Is there a way to define alongside a (typed) structure a contract for the entire structure in Typed Racket? In my particular case, I have a structure that takes two lists as fields, and I want to require the lists to be the same length.

I've looked at:

  • make-struct-type, which allows specification of a "guard" that moderates constructor calls. I could pass a procedure that raises an exception if the lengths don't match, but I don't know what to do with the values returned by make-struct-type.
  • struct/c and the struct form of contract-out, both of which produce structure contracts from contracts on individual fields. This seems unhelpful here.

Ideally I would like to bind the contract to the structure immediately (as in define/contract), but I'm open to adding the contract when I provide the type's procedures. However, I currently provide the recognizer and accessor procedures individually rather than using struct-out (so that I can exclude or rename individual procedures), and I'd like to keep that flexibility.

Right now I have something like this:

(provide
    (rename-out
        [MyStruct my-struct]
        [MyStruct? my-struct?]
        [MyStruct-foo my-struct-foo]
        [MyStruct-bar my-struct-bar]
    )
)

(struct MyStruct (
    [foo : (Listof Symbol)]
    [bar : (Listof Any)]
))
2

2 Answers

2
votes

Wow. I'm surprised how difficult it was to do that in Typed Racket. In plain (untyped) Racket its as simple as adding a #:guard when making you struct. Unfortunately, the struct form in Typed Racket doesn't support it.

So, to deal with this, I would generate a structure type with a private (to the module) constructor name, and then make your own constructor function that actually does the contract you wanted it to check.

This will end up looking something like:

(struct env ([keys : (Listof Symbol)]
             [values : (Listof Any)])
  #:constructor-name internal-env)

(: make-env (-> (Listof Symbol) (Listof Any) env))
(define (make-env k v)
  (unless (= (length k) (length v))
    (raise-arguments-error 'env
                           "env key and value counts don't match"
                           "keys" k
                           "values" v))
  (internal-env k v))

Now, when you provide the struct, simply don't provide internal-env, but do provide the make-env function:

(provide (except-out (struct-out env)
                     internal-env)
         make-env)

Now, when I construct an env, I get a (dynamic) check to ensure the list lengths match:

> (make-env '() '())
#<env>
> (make-env '(a) '(1))
#<env>
> (make-env '(a) '())
env: env key and value counts don't match
  keys: '(a)
  values: '()
0
votes

You may find defining and providing the structure definitions with guard functions from Racket, then require/typed-ing and type annotating the structures in Typed Racket a simpler solution than attempting to achieve the same effect purely in Typed Racket.