I'm trying to define a contract for a struct which inherits from another struct.
#lang racket
(require racket/contract racket/contract/parametric)
(struct semigroup (op))
(struct monoid (mempty) #:super struct:semigroup)
(define (semigroup/c a) (struct/dc semigroup [op (-> a a a)]))
(define (monoid/c a)
(struct/dc monoid [mempty a] [(op #:parent semigroup) (-> a a a)]))
I get an error
struct/dc: expected an identifier that names a field or a sequence with a field name, the #:parent keyword, and the parent
struct
at: (op #:parent semigroup)
in: (struct/dc monoid (mempty a) ((op #:parent semigroup) (-> a a a)))
I fail to see what I'm doing wrong.
I'm using Racket 6.9.
Related
I'm trying to match against a hash table in typed racket, but I keep getting the following error. The code works fine in untyped racket and I've tried changing it up some to no effect. The error looks like it's happening somewhere after the match macro gets expanded but I'm not familiar enough with racket to understand where or how to debug the issue.
Is is possible to use the hash-table pattern in typed racket?
(match (make-hash '((a . 2) (b . 3) (c . 2)))
[(hash-table _ ...) #t])
Type Checker: Polymorphic function `hash-map' could not be applied to arguments:
Domains: HashTableTop (-> Any Any c) Any
HashTableTop (-> Any Any c)
(HashTable a b) (-> a b c) Any
(HashTable a b) (-> a b c)
Arguments: (Mutable-HashTable Symbol Integer) (All (a) (-> a * (Listof a)))
it's impossible.
in the match macro, the hash-table form expands to syntax that includes the hash-map function, viz (lambda (e) (hash-map e list)). this is correct but its type is too abstract for typed racket to infer it. for the type checker to be satisfied, we'd need:
(lambda #:forall (k v) ([e : (HashTable k v)])
(hash-map e
(λ ([k : k] [v : v])
(list k v))))
there's no practical way to specify this, so the hash-table matcher is unusable in typed racket.
if a for loop is usually best, e.g.
(for ([(k v) (make-hash '((a . 2) (b . 3) (c . 2)))] #:when (even? v))
(printf "~a and ~a~n" k v))
or else something like (hash-keys m)
otherwise, positional matching requires advanced knoweldge of typed racket. for example, the following function, hash-set/cond, takes a hash table and arguments of the form (flag k v) ... and updates (if key already in table) or inserts (if key not already in table) each k/v pair if its associated flag is truthy:
(: hash-set/cond (∀ (k v) (->* ((HashTable k v))
#:rest-star (Any k v)
(HashTable k v))))
(define (hash-set/cond ht . args)
(let loop ([ht : (HashTable k v) ht]
[args : (Rec r (U (List* Any k v r) Null)) args])
(if (null? args)
ht
(loop (if (car args)
(hash-set ht (cadr args) (caddr args))
ht)
(cdddr args)))))
e.g.
(hash-set/cond (hash 'a 20 'b "yes")
(even? 3) 'a 10 ; 3's not even, so 'a isn't modified
#t 'b "canary" ; necessarily set 'b to "canary"
'im-a-truthy-value! 'c 'new-value) ; ditto
returns #hash((a . 20) (b . "canary") (c . new-value)).
so if you end-up using typed racket a lot and want to use this kind of functionality, then it can be very useful in certain places! still, typed racket's type system can represent—but not handle—certain recursive hash table types. this is because, when checking the type of the value, hash? cannot refine the type of the hash table beyond it being a hash table with some type of key and value, i.e. hash? : (-> Any Boolean : HashTableTop) instead of (-> Any Boolean : (HashTable k v)). this makes recursing over particular recursively defined JSON schemata impossible. in these cases you must use untyped racket, though the saving grace here is that racket contracts can handle such complex definitions.
if your project is heavily based on complex hash tables, then clojure or janet are likely better language choices.
For example, how can I write a version of map that will work with polymorphic functions in Typed Racket? I use a simple id function defined as:
(: id : (All (A) A -> A))
(define (id x) x)
When I try to map it over a list i get an error:
> (map id '(1 2 3))
Type Checker: Polymorphic function `map' could not be applied to arguments:
Types: (-> a b ... b c) (Listof a) (Listof b) ... b -> (Listof c)
(-> a c) (Pairof a (Listof a)) -> (Pairof c (Listof c))
Arguments: (All (A) (-> A A)) (List One Positive-Byte Positive-Byte)
Expected result: AnyValues
in: (map id (quote (1 2 3)))
You have to manually instantiate the polymorphism in this case:
-> (map (inst identity Integer) '(1 2 3))
- : (Listof Integer) [more precisely: (Pairof Integer (Listof Integer))]
'(1 2 3)
The reason is explained in the Typed Racket Guide here:
Typed Racket’s local type inference algorithm is currently not able to
infer types for polymorphic functions that are used on higher-order
arguments that are themselves polymorphic.
(see docs for more explanation and examples)
I'm trying to load and use a function from a different module at run-time. The issue is that dynamic-require's range, Any, can't seem to be casted to a more specific (function) type.
test.rkt:
#lang typed/racket
(module other-module typed/racket
(provide f)
(: f : Integer -> Integer)
(define (f x)
(* x 2)))
; g has type Any because dynamic-require returns a value of type Any
(define g (dynamic-require '(submod "test.rkt" other-module) 'f))
;contract violation
; Attempted to use a higher-order value passed as `Any` in untyped code: #<procedure:f>
; in: Any
; contract from: typed-world
; blaming: cast
; (assuming the contract is correct)
((cast g (-> Integer Integer)) 3)
Is there any way to load and use a function at run-time from a different module in #lang typed/racket?
One work-around is to do the loading in an untyped module and use require/typed to assign types:
#lang typed/racket
(module other-module typed/racket
(provide f)
(: f : Integer -> Integer)
(define (f x)
(* x 2)))
(module another-module racket
(define g (dynamic-require '(submod "test.rkt" other-module) 'f))
(provide g))
(require/typed 'another-module
(g (-> Integer Integer)))
(g 3)
;; 6
But yeah, it would be nicer if dynamic-require could take a target type or Typed Racket allowed untyped regions (the opposite of with-type).
Given I have Racket typed module
(require typed/racket/unit
Engine/engine2-sig)
(define-unit engine2#
(import)
(export engine2-sig^)
(define-struct posn ([x : Natural] [y : Natural] [z : Natural])
#:transparent)
)
(provide engine2#)
with following signature
#lang typed/racket
(require typed/racket/unit)
(define-signature engine2-sig^
([posn : ???]))
(provide engine2-sig^)
What should I put instead of question marks to export struct called posn?
I cannot find appropriate example in docs here, am I missing something?
I'm trying to add types to some numerical racket code in the hopes of making it faster, but I am stuck dealing with for/list macro expansion in the code below.
(: index-member ((Listof Any) (Listof Any) -> (Listof Index)))
(define (index-member xs ys)
(filter-not negative?
(for/list ([(ann i Index) (in-range (ann (length xs) Index))])
(if (member (list-ref xs i) ys) i -1))))
This function returns a list of indexes foreach x which is a member of y. It works in Racket, but I can't seem to get it past the type checker for Typed Racket. Specifically, the error is:
Type Checker: Error in macro expansion -- insufficient type information to typecheck. please add more type annotations in: (for/list (((ann i Index) (in-range (ann (length xs) Index)))) (if (member (list-ref xs i) ys) i -1))
Can you provide annotations that get this past the type checker and/or explain why these type annotations are insufficient?
The key is to use the for/list: form instead since it allows you to add type annotations over the basic for/list form to give Typed Racket more guidance. I've made a few other adjustments to get the types to line up (e.g., using filter over filter-not, avoiding in-range, etc.):
#lang typed/racket
(: index-member ((Listof Any) (Listof Any) -> (Listof Index)))
(define (index-member xs ys)
(filter index?
(for/list: : (Listof Integer) ([i : Index (length xs)])
(if (member (list-ref xs i) ys) i -1))))
This actually exposes a weakness in the type of filter-not (filter is smarter about the type of the list it returns), which I'll look into fixing.