How to implement "function" monads in racket using functional's data/monad module? - racket

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.

Related

Match Hash Tables in Typed Racket

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.

Occurrence typing with polymorphic union types

Suppose I want to convert the following untyped code into typed racket. These functions are inspired by SICP where they show how a data structure can be constructed purely from functions.
(define (make-pair x y)
(lambda (c)
(cond
((= c 1) x)
((= c 2) y)
(error "error in input, should be 1 or 2"))))
(define (first p) (p 1))
(define (second p) (p 2))
To convert it straight to typed racket, the return value of the make-pair function seems to be (: make-pair (All (A B) (-> A B (-> Number (U A B))))). And following this, the type of first should be (: first (All (A B) (-> (-> Number (U A B)) A))). However, while implementing the function we can't call (p 1) directly now because we need some sort of occurrence typing to make sure first returns only of type A. Changing the return type of first to (U A B) works but then the burden of occurrence typing goes on the user and not in the API. So in this scenario how can we use occurrence typing inside first (that is, how to use a predicate for type variable A) so that we can safely return only the first component of the pair?
UPDATE
I tried an approach which differs a bit from above and requires the predicates for A and B to be supplied as arguments to make-pair function. Below is the code:
#lang typed/racket
(define-type FuncPair (All (A B) (List (-> Number (U A B)) (-> A Boolean) (-> B Boolean))))
(: make-pair (All (A B) (-> A B (-> A Boolean) (-> B Boolean) (FuncPair A B))))
(define (make-pair x y x-pred y-pred)
(list
(lambda ([c : Number])
(cond
((= c 1) x)
((= c 2) y)
(else (error "Wrong input!"))))
x-pred
y-pred))
(: first (All (A B) (-> (FuncPair A B) Any)))
(define (first p)
(let ([pair-fn (car p)]
[fn-pred (cadr p)])
(let ([f-value (pair-fn 1)])
(if (fn-pred f-value)
f-value
(error "Cannot get first value in pair")))))
However, this fails in the check (fn-pred f-value) condition with error expected: A
given: (U A B) in: f-value
From the untyped code at the start of your question, it seems like a pair of A and B is a function that given 1, gives back A, and given 2, gives back B. The way to express this type of function is with a case-> type:
#lang typed/racket
(define-type (Pairof A B)
(case-> [1 -> A] [2 -> B]))
The accessors can be defined the same way as your original untyped code, just by adding type annotations:
(: first : (All (A B) [(Pairof A B) -> A]))
(define (first p) (p 1))
(: second : (All (A B) [(Pairof A B) -> B]))
(define (second p) (p (ann 2 : 2)))
The type of the constructor should be:
(: make-pair : (All (A B) [A B -> (Pairof A B)]))
But the constructor doesn't quite work as-is. One thing wrong with it is that your else clause is missing the else part of it. Fixing that gives you:
(: make-pair : (All (A B) [A B -> (Pairof A B)]))
(define (make-pair x y)
(lambda (c)
(cond
[(= c 1) x]
[(= c 2) y]
[else (error "error in input, should be 1 or 2")])))
This is almost right, and if typed racket were awesome enough, it would be. Typed racket treats equal? specially for occurrence typing, but it doesn't do the same thing for =. Changing = to equal? fixes it.
(: make-pair : (All (A B) [A B -> (Pairof A B)]))
(define (make-pair x y)
(lambda (c)
(cond
[(equal? c 1) x]
[(equal? c 2) y]
[else (error "error in input, should be 1 or 2")])))
Ideally occurrence typing should work with =, but perhaps the fact that things like (= 2 2.0) return true makes that both harder to implement and less useful.

How to apply in typed/racket?

In typed/racket I have a case like [(? procedure? p ) (apply p xv*)]
It will cause error:
Type Checker: Function has no cases in: (apply p xv*)
So I write a test case to detect the reason:
#lang typed/racket
(: test-match-apply-0 (-> (-> Any * Any) (Listof Any) Any))
(define test-match-apply-0
(lambda (x args)
(match x
[(? procedure? p) (apply p args)])))
;; Type Checker: Function has no cases in: (apply p args)
(test-match-apply-0 + (list 1 2 3)) ;; not ok
(apply + (list 2 4)) ;; ok
(: test-match-apply-1 (-> (-> (Listof Any) Any) (Listof Any) Any))
(define test-match-apply-1
(lambda (x args)
(match x
[(? procedure? p) (apply p args)])))
(test-match-apply-1 + (list 1 2 3)) ;; not ok
;; For int is it right
(: test-match-apply-2 (-> (-> (Listof Any) Any) (Listof Number) Number))
(define test-match-apply-2
(lambda (x args)
(match x
[(? procedure? p) (apply p args)])))
(test-match-apply-2 + (list 1 2 3)) ;; not ok
(: test-match-apply-3 (-> (-> Number * Number) (Listof Number) Number))
(define test-match-apply-3
(lambda (x args)
(match x
[(? procedure? p) (apply p args)])))
(test-match-apply-3 + (list 1 2 3)) ;; it is ok
I print the + itself:
> (:print-type +)
(case->
(-> Zero)
(-> Number Number)
(-> Zero Zero Zero)
(-> Number Zero Number)
(-> Zero Number Number)
(-> Positive-Byte Positive-Byte Positive-Index)
(-> Byte Byte Index)
(-> Positive-Byte Positive-Byte Positive-Byte Positive-Index)
(-> Byte Byte Byte Index)
(-> Positive-Index Index Positive-Fixnum)
(-> Index Positive-Index Positive-Fixnum)
(-> Positive-Index Index Index Positive-Fixnum)
(-> Index Positive-Index Index Positive-Fixnum)
(-> Index Index Positive-Index Positive-Fixnum)
(->* (Index Index) (Index) Nonnegative-Fixnum)
.....
Come back to my origin needs, How can I make it [(? procedure? p ) (apply p xv*)] possible in typed/racket? Because in the case I can't detect p 's type. Something like type-apply?
The reason Typed Racket can’t apply that procedure is because it knows nothing about it aside from the fact that it is a procedure. It might not take any arguments, for example, in which case that apply would cause a runtime error. It might take a different kind of argument, or it might even have required keyword arguments. TR doesn’t know any of this just from the procedure? predicate succeeding, so it doesn’t allow you to invoke such a value.
This is tricky, because there is no predicate that will allow you to inspect enough details about the function that will make it safe to apply. You basically have two options:
Constrain the type of the input so that procedure? will restrict it to a specific function type. You can do this by making the input a union of specific types. For example, this typechecks:
(: constrained ((U String Number (String * -> String)) -> String))
(define (constrained x)
(match x
[(? string?) x]
[(? number?) (number->string x)]
[(? procedure?) (apply x '("a" "b" "c"))]))
Even though the type is a union type here, since there is only one possible case for which the procedure? predicate is true, TR can restrict the type to a properly applicable value.
The type of the function itself can get pretty fancy, and TR can still figure it out. For example, it still works with a polymorphic type:
(: poly-constrained (All [a] (U String Number (a * -> String)) (Listof a) -> String))
(define (poly-constrained x lst)
(match x
[(? string?) x]
[(? number?) (number->string x)]
[(? procedure?) (apply x lst)]))
Alternatively, you can use cast. This will allow you to tell TR to perform a dynamic check that a value matches a particular type.
(: unconstrained (Any -> String))
(define (unconstrained x)
(match x
[(? string?) x]
[(? number?) (number->string x)]
[(? procedure?) (apply (cast x (String * -> String)) '("a" "b" "c"))]))
However, note that this is a little big dangerous! There are a couple pitfalls to using cast:
The check generates a typed/untyped boundary for a single value, effectively the same sort of boundary between typed and untyped modules. This means that cast generates a contract, which is checked at runtime, which, unlike static types, takes time and can reduce performance significantly if used in a tight loop.
Since cast performs the check dynamically, you lose one of the main benefits of Typed Racket: static type safety. If, for example, someone provides a procedure that does not match the given type, a runtime error will occur, which is precisely the sort of thing Typed Racket is designed to prevent.
If possible, you probably want to use the first approach so that you don’t compromise type safety, but in cases where predicates are not good enough, you can use cast. Just be aware of the downsides before you choose it.

for/list annotations in typed/racket

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.

Typed Racket: Creating generic types with define-type

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.