Reputation: 413
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)]
))
Upvotes: 3
Views: 442
Reputation: 1
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.
Upvotes: 0
Reputation: 22342
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: '()
Upvotes: 2