I'm working on defining a language which has casts and subtyping as follows:
(define-language base
(t ::= int any)
(e ::= number (cast t e))
#| stuff ... |#)
I then define the following judgment form over it:
(define-judgment-form base
#:mode (<: I I)
#:contract (<: t t)
[------
(<: t t)]
[------
(<: int any)])
And now, in my reduction relation, I want to write something like
(define b-> (reduction-relation base
(--> (cast t v)
v
(where-judgment-holds (<: (typeof v) t)))))
Where we assume that (typeof v) returns the type of the value v. As far as I can tell, there isn't anything like where-judgment-holds in the Redex library, and while I can work around it with an unquote, it would be nice if there was something built into Redex.
What you are looking for actually is called judgment-holds, and it works in your example exactly where you put where-judgment-holds:
(define b-> (reduction-relation base
(--> (cast t v)
v
(judgment-holds (<: (typeof v) t)))))
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.
While taking a look at PLT redex, I wanted to play with simplification rules; so I defined this minimal language for booleans:
(define-language B0
(b T F (not b)))
I wanted to simplify a chain of (not (not ...)) so I extended the language to deal with contexts and defined a reduction relation to simplify the not:
(define-extended-language B1 B0
(C (not C) hole)
(BV T F))
(define red0
(reduction-relation
B1
(--> (in-hole C (not T)) (in-hole C F))
(--> (in-hole C (not F)) (in-hole C T))))
Now I wanted to extend my language to boolean equations and to allow not-simplification at each side of the equation, so I defined:
(define-extended-language B2 B1
(E (= C b) (= b C)))
hoping that:
(define red1
(extend-reduction-relation red0 B2))
will do the thing.
But no: red1 can reduce (not (not (not F))))) but not (= (not T) F)))
Am I doing something really silly here?
The problem with red1 is that it only contains the rules of red0 which use the limited context C. To make it work as expected you could either add the old rules modified to use E or make somehow the final extended context have the name C. One not very tedious approach could be:
(define-language L)
(define R
(reduction-relation L
(--> (not T) F)
(--> (not F) T)))
(define-language LB
(b T F (not b))
(C (compatible-closure-context b)))
(define RB (context-closure R LB C))
(define-extended-language LBE LB
(e (= b b))
(C .... (compatible-closure-context e #:wrt b)))
(define RBE (extend-reduction-relation RB LBE))
Note that this doesn't work in some older versions.
Two sources of useful information are this tutorial and of course the redex reference.
Since the data/monad module's do notation operates on structures, how can I define monad types that are functions, e.g. like parsers?
I'm used to OCaml, where my monad would have had roughly the following signature:
module type Parser = sig
type state = string * int
type 'a t = state -> (('a * state), string) Result.t
val return: 'a -> 'a t
val bind: 'a t -> ('a -> 'b t) -> 'b t
end
I'm sorry to post an example in OCaml, my racket abilities are not great for now.
Is this kind of monad compatible with data/monad, or should I look at another solution?
There’s nothing that prevents you from wrapping a function in a structure, then implementing the gen:monad interface on that structure. The megaparsack library is an example that uses that technique to implement a monadic parser combinator library. Specifically, take a look at the parser structure definition:
(struct parser (proc)
#:methods gen:functor
[(define/generic -map map)
(define (map f p)
(parser (compose (match-lambda [(consumed (ok v rest message)) (consumed (ok (-map f v) rest message))]
[(empty (ok v rest message)) (empty (ok (-map f v) rest message))]
[error error])
(parser-proc p))))]
#:methods gen:applicative
[(define (pure _ x)
(pure/p x))
(define (apply p ps)
(do [f <- p]
[xs <- (map/m values ps)]
(d:pure (r:apply f xs))))]
#:methods gen:monad
[(define (chain f p)
(parser
(λ (input)
(match (parse p input)
[(empty (ok (and foo (syntax-box x _)) rest message))
(match (parse (f x) rest)
[(empty reply) (empty (merge-message/reply message reply))]
[consumed consumed])]
[(consumed (ok (and foo (syntax-box x srcloc)) rest message))
(consumed (match (parse (f x) rest)
[(consumed (ok stx rest message))
(ok (merge-syntax-box/srcloc stx srcloc) rest message)]
[(empty (ok (syntax-box datum _) rest message))
(merge-message/reply message (ok (syntax-box datum srcloc) rest message))]
[(consumed error) error]
[(empty error) (merge-message/reply message error)]))]
[error error]))))])
This defines a structure type named parser containing a single field, proc, and implements the gen:functor, gen:applicative, and gen:monad interfaces on that structure type. The single field contains the parser procedure.
A common way of defining semantics is (for example):
return v if [some other condition]
otherwise, return error
For example, consider
(define-language simple-dispatch
(e ::= v (+ e e))
(v ::= number string)
(res ::= e err)
(E ::= hole (+ E e) (+ v E)))
We could then define the reduction relation
(define s-> (reduction-relation simple-dispatch
#:domain res
(--> (in-hole E (+ number_1 number_2))
(in-hole E ,(+ number_1 number_2)))
(--> (in-hole E (+ any any))
err)))
This is the natural way to do this, because it avoids having to write individual matchers for each of the 3 failure cases (number string, string number, string string). However, it then creates the problem that running it like this:
(apply-reduction-relation s-> (term (+ 2 2)))
Shows (correctly) that it lets you reduce both to an error or to the number 4. Is there a way to make an "except" pattern that avoids having to check all of the constituent cases?
What you want to use here is a combination of side-condition and redex-match?. Extending your reduction-relation gives:
(define s-> (reduction-relation simple-dispatch
#:domain res
(--> (in-hole E (+ number_1 number_2))
(in-hole E ,(+ (term number_1) (term number_2))))
(--> (in-hole E (+ any_1 any_2))
err
(side-condition
(not (redex-match? simple-dispatch
(+ number number)
(term (+ any_1 any_2))))))))
This just says you can take the second rule so long as the first one is not true, which is what the papers are saying implicitly, and just didn't draw out explicitly in the figure. (Note that you can use side-condition/hidden to get it to not draw the side condition when rendering the figure).
You can use this method to scale up to any number of patterns you want to disallow.
I'm trying to get a bit into Typed Racket, but I'm having some trouble getting an (admittedly rather constructed) experiment to work.
This is what I originally had:
#lang typed/racket
(: generate-list
(All (A)
((A -> A) (Integer -> A) Integer -> (Listof A))))
(define (generate-list function location-function num-items)
(let: loop : (Listof A)
((count : Integer 0)
(result : (Listof A) (list)))
(if (>= count num-items)
(reverse result)
(loop (+ count 1)
(cons (function (location-function count)) result)))))
; ---------------------------------
(: f (Number -> Number))
(define (f x) (* x x))
(: locf (Integer -> Number))
(define (locf x) x)
; ---------------------------------
(displayln (generate-list f locf 10))
Which has the output:
(0 1 4 9 16 25 36 49 64 81)
Which is nice. Then I figured I could make this a bit better documented by giving the function and location-function a defined type:
#lang typed/racket
(define-type (ListGenFunction A) (A -> A))
(define-type (ListGenLocFunction A) (Integer -> A))
(: generate-list
(All (A)
(ListGenFunction ListGenLocFunction Integer -> (Listof A))))
(define (generate-list function location-function num-items)
(let: loop : (Listof A)
((count : Integer 0)
(result : (Listof A) (list)))
(if (>= count num-items)
(reverse result)
(loop (+ count 1)
(cons (function (location-function count)) result)))))
; ----------- Numbers! ------------
(: f ListGenFunction)
(define (f x) (* x x))
(: locf ListGenLocFunction)
(define (locf x) x)
; ---------------------------------
(displayln (generate-list f locf 10))
Now here's where the problems start (and I really hope some experienced Typed Racketeers aren't facepalming too hard right now). For one, the type checker gives me an error on the line where I define f. The message is rather lengthy, but it's basically: "Type Checker: No function domains matched in function application: Types: ... in: (* x x)". I thought I defined a type that has one parameter of a generic type A that returns a generic type A? Wouldn't (* x x) work? Or is there some need to "tag" the type? (Like in C++-like languages where it's list<int> for example)
On top of that: Now my type-definition for the function generate-list has a return type of "(Listof A)". But that A is not at all declared to be the same A that the parameters with the types ListGenFunction and ListGenLocFunction expect. I kind of want to make that connection, however, so that anyone who uses that function can be sure that the types of his provided functions match the type of the returning list items.
How do I do this correctly?
PS:
I'm not sure if I described my intention in the last paragraph so that anyone can understand it. But if you take some generic pseudo-C++-like code, I want to get the following:
list<T> generate-list(LGF<T> f, LGLF<T> locf, int count) { ... }
So that all T's are exactly the same.
There are two problems here, both of which stem from the same confusion. You're using a generic type, ListGenFunction, without telling Typed Racket (or the reader of your program) what particular type you're using it with.
For example, f isn't an arbitrary ListGenFunction, it's a ListGenFunction that works specifically on numbers. So you should write:
(: f (ListGenFunction Integer))
and
(: locf (ListGenLocFunction Integer))
Similarly, you should give generate-list a type like this:
(: generate-list
(All (A)
((ListGenFunction A) (ListGenLocFunction A) Integer -> (Listof A))))
This is just like how you explicitly say that you're producing a (Listof A), not just a Listof.