[racket] TR: struct: #:prefab option
On 2013-05-06 08:46:04 -0700, Eric Dobson wrote:
> Couldn't this be solved by protecting the struct on export?
I'm not sure this is sufficient. Consider the following code:
#lang racket/load
(module foo racket
(struct foo ([f #:mutable]) #:prefab)
(define y (foo values))
(define (mutater) (set-foo-f! y (λ (x) (string->number x))))
(provide y mutater))
(module bar typed/racket
(struct: foo ([f : (Float -> Float)]) #:mutable #:prefab)
(require/typed 'foo
[y foo]
[mutater (-> Void)])
(mutater)
((foo-f y) 5.3))
The use of mutation lets you sneak values that don't match the type.
Even if you require the struct instance with `struct/dc` and apply an
impersonator-based contract, you can't prevent the un-impersonated
access on the `foo` side.
This actually looks like it's a problem even without #:prefab structs:
#lang racket/load
(module foo racket
(struct foo ([f #:mutable]))
(define y (foo values))
(define (mutater) (set-foo-f! y (λ (x) (string->number x))))
(provide y (struct-out foo) mutater))
(module bar typed/racket
(require/typed 'foo
[#:struct foo ([f : (Float -> Float)])]
[y foo]
[mutater (-> Void)])
(mutater)
((foo-f y) 5.3))
Another thing to be careful of is struct inheritance. For example:
#lang racket/load
(module foo typed/racket
(struct: foo ([f : (Float -> Float)]) #:prefab)
(struct: foo2 foo ([g : (Float -> Float)]) #:prefab)
(define x (ann (foo2 (λ (x) (* x 5.3))
(λ (x) (* x 5.3)))
foo))
;; Note that this is a type error
;(foo2-g x)
(provide x))
(module bar racket
(require 'foo)
(struct foo (f) #:prefab)
(struct foo2 foo (g) #:prefab)
((foo2-g x) "foo"))
In this case, `foo2` inherits from `foo` and so you can upcast an
instance of `foo2` to `foo`. But then you forget a field, and if the
type->contract translation isn't smart, then you may fail to protect all
the fields (that an untyped client can now guess and misuse).
Cheers,
Asumu