(aux category list): the list monad
Introduction
This section tests the list monad, which is a monad that represents non-deterministic computations. A value of type (list a) represents a non-deterministic computation that can produce any of the values in the list. The monad operations allow us to combine these computations in various ways, such as sequencing them with >>= or combining them with ⊕/monad.
Implementation
The list monad is implemented as follows, which defines the monad operations for lists. The return operation creates a singleton list, and the >>= operation uses append-map to apply a function to each element of the list and concatenate the results. The ⊕/monad operation is defined as append, which combines two lists of computations.
(module
(aux category list)
*
(import scheme (chicken base) (only srfi-1 append-map) (aux base))
(define (return v) (list v))
(define (>>= m f) (append-map f m))
(define (fail . args) '())
(define mempty '())
(define (mappend l1 l2) (append l1 l2))
(define (mlog x) (list x))
(define map/functor map)
(define (map/applicative flst lst) (append-map (λ (f) (map f lst)) flst))
(define ⊕₀/monad '())
(define (⊕/monad m1 m2) (append m1 m2)))
test/empty: pass
Empty list is the identity of list monad.(define (test/empty _) (⊦= '() (do/monad)))
((eta 0.0) (memory #(12582912 2180256 1048576)) (stdout "") (stderr ""))
test/return: pass
Return lifts a value into the list monad, which creates a singleton list.(define (test/return _) (⊦= '(4) (do/monad ,4)))
((eta 0.0) (memory #(12582912 2181568 1048576)) (stdout "") (stderr ""))
test/two-returns: pass
Multiple return statements in a do/monad block are implicitly combined with >>=, so the above is equivalent to (return 1) >>= (λ x (let y x) (return (+ x y))).(define (test/two-returns _) (⊦= '(2) (do/monad (← x ,1) (let y x) ,(+ x y))))
((eta 0.0) (memory #(12582912 2183048 1048576)) (stdout "") (stderr ""))
test/let-pattern: pass
A let clause may destructure a pure value with a pattern, binding its variables for the rest of the block.(define (test/let-pattern _)
(⊦= '(3 12)
(do/monad
(← x (return/⊕ 1 4))
(let (,a unquote b) (cons x (* 2 x)))
,(+ a b))))
((eta 0.0) (memory #(12582912 2184824 1048576)) (stdout "") (stderr ""))
test/let-unquote: pass
As in ← clauses, the right-hand side of a let clause may be written ,expr; since let binds a pure value, ,expr is the same as expr.(define (test/let-unquote _)
(⊦= '((3 1 2))
(do/monad (let a ,1) (let (,b unquote c) ,(cons 2 3)) ,(list c a b))))
((eta 0.0) (memory #(12582912 2186400 1048576)) (stdout "") (stderr ""))
test/trailing-binding: pass
A ← or let clause binds variables for the clauses that follow it, so ending a block with one is rejected when the block is expanded; a trailing ordinary let form is still just an expression.(define (test/trailing-binding _)
(let ((rejected?
(λ (form)
(condition-case
(begin (eval form) #f)
(e (exn syntax)
(and (substring-index
"do/monad: a binding clause cannot end the block"
((condition-property-accessor 'exn 'message) e))
#t))))))
(⊨ (rejected? '(do/monad (← x (list 1 2)))))
(⊨ (rejected? '(do/monad (← (,x) ,1))))
(⊨ (rejected? '(do/monad (← x ,1) (let y x))))
(⊨ (rejected? '(do/monad (← x ,1) (let (,a unquote b) (cons x 2)))))
(⊨ (rejected? '(do/monad (← x ,1) (let (,a ,b) (list x 2)))))
(⊨ (rejected?
'(do/monad (← x ,1) (let ((,a ,b) unquote c) (list (list x 2) 3)))))
(⊨ (rejected? '(do/monad (← x ,1) (let (_ unquote b) (cons x 2)))))
(⊨ (rejected? '(do/monad (← x ,1) (let (a ,b) (list x 2)))))
(⊦= '(2) (do/monad (← x ,1) (let ((y x)) (list (+ x y)))))))
((eta 0.002) (memory #(12582912 2207936 1048576)) (stdout "") (stderr ""))
test/pattern-mismatch: pass
A value that does not match the pattern of a ← clause is handed to fail, which is the empty list in the list monad, so non-matching shapes are skipped instead of raising an error.(define (test/pattern-mismatch _)
(⊦= '((1 2) (3 4))
(do/monad (← (,x unquote xs) '((1 2) () (3 4))) ,(cons x xs))))
((eta 0.0) (memory #(12582912 2186216 1048576)) (stdout "") (stderr ""))
test/return/⊕: pass
The return/⊕ function lifts a value into the list monad, but it also allows you to specify multiple values to lift, which are combined with ⊕/monad (which is append for lists). So the above is equivalent to (return/⊕ 1 2 3) >>= (λ x (return/⊕ 4 5 6) >>= (λ y (return/⊕ (+ x y) (- x y)))).(define (test/return/⊕ _)
(⊦= '(((1 4) 5 -3)
((1 5) 6 -4)
((1 6) 7 -5)
((2 4) 6 -2)
((2 5) 7 -3)
((2 6) 8 -4)
((3 4) 7 -1)
((3 5) 8 -2)
((3 6) 9 -3))
(do/monad
(← x (return/⊕ 1 2 3))
(← y (return/⊕ 4 5 6))
(return/⊕ `((,x ,y) ,(+ x y) ,(- x y))))))
((eta 0.0) (memory #(12582912 2187064 1048576)) (stdout "") (stderr ""))
test/fmap: pass
The map/monad function is the functor operation for the list monad, which applies a function to each value in the list. So the above is equivalent to (map add1 (return/⊕ 1 2 3)).(define (test/fmap _) (⊦= '(2 3 4) (map/monad add1 (return/⊕ 1 2 3))))
((eta 0.0) (memory #(12582912 2188528 1048576)) (stdout "") (stderr ""))
test/<$>: pass
The <$> function is Haskell's name for map/monad, the functor operation, which maps over the monad with a single bind.(define (test/<$> _)
(⊦= '(2 3 4) (<$> add1 (return/⊕ 1 2 3)))
(⊦= '() (<$> add1 '())))
((eta 0.0) (memory #(12582912 2189960 1048576)) (stdout "") (stderr ""))
test/<*>: pass
The <*> function is the applicative functor operation. It takes a list of functions and a list of values, and applies each function to each value, returning the combined results. So the above is equivalent to(define (test/<*> _)
(⊦= '(2 3 4 0 1 2) (<*> (return/⊕ add1 sub1) (return/⊕ 1 2 3))))
((eta 0.0) (memory #(12582912 2191352 1048576)) (stdout "") (stderr ""))
test/kleisli: pass
The o/monad function composes Kleisli arrows right to left, like function composition, while ◇ composes them left to right, so (◇ f g) runs f first; with no arrows both are return.(define (test/kleisli _)
(let ((f (λ (x) (list x (* 10 x)))) (g (λ (x) (list (add1 x)))))
(⊦= '(4 31) ((o/monad g f) 3))
(⊦= '(4 31) ((◇ f g) 3))
(⊦= '(3) ((◇) 3))))
((eta 0.0) (memory #(12582912 2190376 1048576)) (stdout "") (stderr ""))
test/list->monad: pass
The monoids->monad function folds a list with a monoid inside the monad; list->monad is its instance at the list monoid ('() , cons).(define (test/list->monad _)
(⊦= '((1 2 3)) (list->monad '(1 2 3)))
(⊦= '(()) (list->monad '()))
(⊦= '(6) ((monoids->monad 0 +) '(1 2 3))))
((eta 0.0) (memory #(12582912 2192112 1048576)) (stdout "") (stderr ""))
test/lift/monad: pass
The lift/monad function is a convenient way to lift a multi-argument function into the monad. It takes a function and multiple monadic values, and applies the function to the values inside the monad. So the above is equivalent to (return/⊕ 1 2 3) >>= (λ x (return/⊕ 4 5 6) >>= (λ y (return (+ x y))))).(define (test/lift/monad _)
(⊦= '(5 6 7 6 7 8 7 8 9) (lift/monad + (return/⊕ 1 2 3) (return/⊕ 4 5 6))))
((eta 0.0) (memory #(12582912 2193904 1048576)) (stdout "") (stderr ""))
test/powerset: pass
The powerset/monad function generates the powerset of a list, which is the set of all subsets of the list. It does this by using filter/monad to filter the list with a predicate that non-deterministically returns true or false for each element, effectively including or excluding each element in the subsets. So the above is equivalent to (filterM (λ (_) (return/⊕ #f #t)) '(1 2 3 4 5)).(define (test/powerset _)
(⊦= '(()
(5)
(4)
(4 5)
(3)
(3 5)
(3 4)
(3 4 5)
(2)
(2 5)
(2 4)
(2 4 5)
(2 3)
(2 3 5)
(2 3 4)
(2 3 4 5)
(1)
(1 5)
(1 4)
(1 4 5)
(1 3)
(1 3 5)
(1 3 4)
(1 3 4 5)
(1 2)
(1 2 5)
(1 2 4)
(1 2 4 5)
(1 2 3)
(1 2 3 5)
(1 2 3 4)
(1 2 3 4 5))
(powerset/monad '(1 2 3 4 5))))
((eta 0.0) (memory #(12582912 2195344 1048576)) (stdout "") (stderr ""))
(aux category maybe): the maybe monad
Introduction
This section tests the maybe monad, where (just . v) is a computation that succeeded with the value v and the empty list is a computation that failed, short-circuiting every later bind.
Implementation
(module
(aux category maybe)
*
(import scheme (chicken base) (aux base))
(define (return v) `(just unquote v))
(define (>>= m f) (match/first m ((just unquote v) (f v)) (else '())))
(define (fail . args) '()))
test/bind: pass
A failure anywhere in the block makes the whole block fail.(define (test/bind _)
(⊦= '(just . 3) (do/monad (← x ,1) (← y (return 2)) ,(+ x y)))
(⊦= '() (do/monad (← x ,1) (← y (fail)) ,(+ x y))))
((eta 0.0) (memory #(6291456 1875416 1048576)) (stdout "") (stderr ""))
test/return: pass
return takes exactly one value, like in every other monad instance.(define (test/return _)
(let ((arity-error?
(λ args
(condition-case
(begin (apply return args) #f)
(e (exn arity) #t)))))
(⊨ (arity-error? 1 2 3))
(⊨ (arity-error?))))
((eta 0.0) (memory #(6291456 1883056 1048576)) (stdout "") (stderr ""))
(aux category prob): the probability monad
Introduction
This section tests the probability monad, where a value of type (Prob a) is a distribution: an association list of outcomes paired with their probabilities. Binding multiplies the probabilities along each path and consolidates outcomes that share the same value by summing their probabilities.
Implementation
(module
(aux category prob)
*
(import
scheme
(chicken base)
(chicken fixnum)
(only srfi-1 append-map find)
(only srfi-69
make-hash-table
equal?-hash
hash-table-ref/default
hash-table-set!)
(aux base))
(define (return v) (list (cons v 1)))
(define (>>= m f)
(consolidate
(append-map
(λ1-match/first
((,v unquote p)
(map (λ1-match/first ((,v* unquote p*) (cons v* (* p p*)))) (f v))))
m)))
(define (fail . args) '())
(define (coin/tf p) (list (cons #t p) (cons #f (- 1 p))))
(define (consolidate m)
(let loop ((m m) (acc '()) (n 0) (cells #f))
(cond ((null? m) (reverse acc))
((and (not cells) (fx> n 60))
(let1 (cells (make-hash-table equal? equal?-hash))
(for-each
(λ (cell) (hash-table-set! cells (car cell) cell))
acc)
(loop m acc n cells)))
(else
(let* ((vp (car m))
(v (car vp))
(p (cdr vp))
(cell (if cells
(hash-table-ref/default cells v #f)
(find (λ1-match/first ((,v* . _) (equal? v v*)))
acc))))
(cond (cell
(set-cdr! cell (+ (cdr cell) p))
(loop (cdr m) acc n cells))
(else
(let1 (cell (cons v p))
(when cells (hash-table-set! cells v cell))
(loop (cdr m) (cons cell acc) (fx+ n 1) cells))))))))))
test/return: pass
Return is the certain distribution, with the exact probability 1 so that the right identity law (>>= m return) = m holds for exact distributions too.(define (test/return _)
(⊨ (equal? '((a . 1)) (return 'a)))
(let ((d '((a . 1/2) (b . 1/2)))) (⊨ (equal? d (>>= d return)))))
((eta 0.0) (memory #(6291456 1885648 1048576)) (stdout "") (stderr ""))
test/exact: pass
Distributions built from exact rationals stay exact through binds, and their total probability is exactly 1.(define (test/exact _)
(let ((d (do/monad (← x (coin/tf 1/3)) (← y (coin/tf 1/7)) ,(and x y))))
(⊨ (equal? '((#t . 1/21) (#f . 20/21)) d))
(⊨ (exact? (apply + (map cdr d))))))
((eta 0.0) (memory #(6291456 1887264 1048576)) (stdout "") (stderr ""))
test/consolidate: pass
consolidate sums the probabilities of outcomes sharing a value, in first-occurrence order and without mutating its argument; past about 60 distinct values it finds the summed cells through a hash table instead of scanning them.(define (test/consolidate _)
(let* ((up (map (λ (i) (cons i 1/300)) (iota 100)))
(down (map (λ (i) (cons (- 99 i) 1/150)) (iota 100)))
(m (append up down))
(m* (map (λ (vp) (cons (car vp) (cdr vp))) m)))
(⊨ (equal?
'((#f . 0.5) (#t . 0.5))
(consolidate '((#f . 0.25) (#t . 0.5) (#f . 0.25)))))
(⊨ (equal? (map (λ (i) (cons i 1/100)) (iota 100)) (consolidate m)))
(⊨ (equal? m* m))))
((eta 0.0) (memory #(6291456 1907856 1048576)) (stdout "") (stderr ""))
(aux category ziplist): the ziplist monad
Introduction
This section tests the ziplist monad, whose bind zips rather than nests: the i-th element of (>>= m g) is the i-th element of (g v), where v is the i-th element of m. A pure value is the circular list that repeats it, so it zips with lists of any length.
Implementation
(module
(aux category ziplist)
*
(import scheme (chicken base) (only srfi-1 take every iota) (aux base))
(define (list-tail/or-false lst i)
(cond ((null? lst) #f)
((zero? i) lst)
(else (list-tail/or-false (cdr lst) (sub1 i)))))
(define (list-rho lst)
(let meet ((t lst) (h lst))
(cond ((or (null? h) (null? (cdr h))) #f)
((eq? (cdr t) (cddr h))
(let* ((t (cdr t))
(start (let S ((a lst) (b t) (n 0))
(if (eq? a b) n (S (cdr a) (cdr b) (add1 n)))))
(period
(let P ((b (cdr t)) (n 1))
(if (eq? b t) n (P (cdr b) (add1 n))))))
(cons start period)))
(else (meet (cdr t) (cddr h))))))
(define (>>=/circular m start period g)
(let* ((images (list->vector (map g (take m (+ start period)))))
(at (λ (i)
(vector-ref
images
(if (< i start) i (+ start (modulo (- i start) period))))))
(rhos (map (λ (k) (list-rho (vector-ref images k)))
(iota period start)))
(from (and (every pair? rhos) (apply max start (map car rhos))))
(cycle (and from (apply lcm period (map cdr rhos))))
(bound (and from (+ from cycle)))
(d (let D ((i 0) (acc '()))
(cond ((eqv? i bound) (reverse acc))
((list-tail/or-false (at i) i)
=>
(λ (t) (D (add1 i) (cons (car t) acc))))
(else (reverse acc))))))
(when (and bound (= (length d) bound))
(set-cdr! (list-tail d (sub1 bound)) (list-tail d from)))
d))
(define (return x) (list/circular x))
(define (>>= m g)
(match/first
m
((,l unquote ,m) (g l))
(else (match/first
(list-rho m)
((,start unquote period) (>>=/circular m start period g))
(else (let D ((m* m) (i 0) (acc '()))
(cond ((null? m*) (reverse acc))
((list-tail/or-false (g (car m*)) i)
=>
(λ (t) (D (cdr m*) (add1 i) (cons (car t) acc))))
(else (reverse acc)))))))))
(define (fail . args) '()))
test/pure: pass
A pure value repeats forever as a one-element circular list, so write never returns on it; zipping it with a finite list gives a finite result.(define (test/pure _)
(let ((l (do/monad (← x ,3) (← y ,4) (← z ,5) ,(+ x y z))))
(⊨ (eq? l (cdr l)))
(⊦= 12 (car l))
(⊦= '(12 13 14) (do/monad (← x l) (← y '(0 1 2)) ,(+ x y)))))
((eta 0.0) (memory #(6291456 1990504 1048576)) (stdout "") (stderr ""))
test/circular: pass
A circular list of any period zips too: the diagonal is finite as soon as an inner list of the cycle is, otherwise it is circular again, so (>>= m return) gives back m.(define (test/circular _)
(⊦= '(1 2) (>>= (list/circular 1 2) (λ (x) (list x x))))
(⊦= '((1 a) (2 b) (1 c))
(do/monad (← x (list/circular 1 2)) (← y '(a b c)) ,(list x y)))
(let ((r (>>= (list/circular 1 2) return)))
(⊦= '(0 . 2) (list-rho r))
(⊦= '(1 2) (take r 2)))
(let ((r (do/monad
(← x (list/circular 1 2 3))
(← y (list/circular 'a 'b))
,(list x y))))
(⊦= '(0 . 6) (list-rho r))
(⊦= '((1 a) (2 b) (3 a) (1 b) (2 a) (3 b) (1 a)) (take r 7)))
(let ((r (>>= (cons 0 (cons 9 (list/circular 1 2 3)))
(λ (x) (cons x (cons x (list/circular x 'y)))))))
(⊦= '(2 . 6) (list-rho r))
(⊦= '(0 9 1 y 3 y 2 y 1 y) (take r 10))))
((eta 0.0) (memory #(6291456 1992184 1048576)) (stdout "") (stderr ""))
test/diagonal: pass
Binding zips the lists, truncating at the shortest one.(define (test/diagonal _)
(⊦= '((1 a) (2 b)) (do/monad (← x '(1 2 3)) (← y '(a b)) ,(list x y)))
(⊦= '(2 1 2) (<*> (list add1 sub1 sub1) (list 1 2 3))))
((eta 0.0) (memory #(6291456 1993416 1048576)) (stdout "") (stderr ""))
test/false-elements: pass
A #f element is a value like any other, so predicates zip too.(define (test/false-elements _)
(⊦= '(#f #t #f) (do/monad (← x '(1 2 3)) ,(even? x)))
(⊦= '(#f #f) (>>= '(1 2) (λ (v) (list #f #f)))))
((eta 0.0) (memory #(6291456 1994744 1048576)) (stdout "") (stderr ""))
test/truncation: pass
The diagonal stops at the first inner list too short to reach it, rather than skipping it and sliding the later elements out of their index; a value that does not match the pattern of a ← clause goes to fail, the empty list, so the diagonal stops there too.(define (test/truncation _)
(⊦= '(x) (>>= '(1 2 3) (λ (v) (if (= v 2) '(a) '(x y z w)))))
(⊦= '((1 a c d) (2 b c* e))
(do/monad
(← x '(1 2 3))
(← y '(a b))
(← z '(c c*))
(← r '(d e f g))
,(list x y z r)))
(⊦= '(1) (do/monad (← (,a unquote b) '((1 . 2) 3 (4 . 5))) ,a)))
((eta 0.0) (memory #(6291456 1996688 1048576)) (stdout "") (stderr ""))
test/associativity: pass
Both sides of the associativity law agree index by index. On ragged inner lists one side may stop earlier: the inner bind on the right truncates at the first index any of its inner lists misses, even when the left side never looks at that index, which is why finite zip lists admit no lawful monad (Haskell's ZipList is only an Applicative).(define (test/associativity _)
(let* ((table '#((1 2 0) (2) (0 1 2 1) (3 0 3 2 1)))
(f (λ (v) (vector-ref table v)))
(g (λ (v) (reverse (vector-ref table v)))))
(for-each
(λ (m)
(let ((lhs (>>= (>>= m f) g)) (rhs (>>= m (λ (x) (>>= (f x) g)))))
(⊦= (map (λ (l r) l) lhs rhs) (map (λ (l r) r) lhs rhs))))
'((0 1 2 3) (2 0 1) (0 2 0 1) (1 0 2) (3 3 3 0) (2 2 2) (3 0)))
(⊦= '(1 2 3) (>>= (>>= '(3 3 3) f) g))
(⊦= '(1 2 3) (>>= '(3 3 3) (λ (x) (>>= (f x) g))))))
((eta 0.0) (memory #(6291456 1996040 1048576)) (stdout "") (stderr ""))
(aux category difflist): difference lists
Introduction
This section tests difference lists: a difflist is a function that prepends its content to the list it receives, so appending two of them is function composition, in constant time, and difflist->list applies the result to '().
Implementation
(module
(aux category difflist)
*
(import
scheme
(chicken base)
(chicken pretty-print)
(only srfi-1 append-map append-reverse)
(aux base))
(define-record category-difflist f)
(set-record-printer!
category-difflist
(λ (d port)
(let ((lst (difflist->list d)) (D (display/port port)))
(D "⸨")
(for-each (μ v (D " ") (D v)) lst)
(D " ⸩"))))
(define (return x) (make-category-difflist (λ (x*) (cons x x*))))
(define (>>= m g)
(let* ((f (category-difflist-f m)) (xs (map g (f '()))))
(make-category-difflist
(λ (xs*) (foldr (λ (m* lst) ((category-difflist-f m*) lst)) xs* xs)))))
(define (fail . args) mempty)
(define mempty (make-category-difflist (λ (lst) lst)))
(define (mappend l1 l2)
(make-category-difflist
(o (category-difflist-f l1) (category-difflist-f l2))))
(define (mlog x) (make-category-difflist (λ (xs) (cons x xs))))
(define (list->difflist l1) (make-category-difflist (λ (l2) (append l1 l2))))
(define (difflist->list dl) ((category-difflist-f dl) '()))
(define (reverse/difflist dl)
(make-category-difflist
(λ (xs) (append-reverse ((category-difflist-f dl) '()) xs)))))
test/monoid: pass
Appending is composition, and mempty is the identity.(define (test/monoid _)
(⊦= '(1 2 3 4 5)
(difflist->list
(mappend (list->difflist '(1 2 3)) (list->difflist '(4 5)))))
(⊦= '(1 2) (difflist->list (mappend mempty (list->difflist '(1 2)))))
(⊦= '(1 2) (difflist->list (mappend (list->difflist '(1 2)) mempty))))
((eta 0.0) (memory #(6291456 1918504 1048576)) (stdout "") (stderr ""))
test/fail: pass
A failure is the empty difflist, so it drops only its own branch, as '() does in the list monad.(define (test/fail _)
(⊦= '() (difflist->list (fail)))
(⊦= '(1 2) (difflist->list (mappend (fail) (list->difflist '(1 2)))))
(⊦= '(1 3)
(difflist->list
(>>= (list->difflist '(1 2 3))
(λ (x) (if (= x 2) (fail) (return x))))))
(⊦= '(1 3 9 9)
(difflist->list
(mappend
(>>= (list->difflist '(1 2 3))
(λ (x) (if (= x 2) (fail) (return x))))
(list->difflist '(9 9))))))
((eta 0.0) (memory #(6291456 1920024 1048576)) (stdout "") (stderr ""))
test/reverse: pass
Reversing a difflist reverses its own content only, not the tail it is later given.(define (test/reverse _)
(⊦= '(3 2 1) (difflist->list (reverse/difflist (list->difflist '(1 2 3)))))
(⊦= '(3 2 1 4 5)
(difflist->list
(mappend
(reverse/difflist (list->difflist '(1 2 3)))
(list->difflist '(4 5)))))
(⊦= '(4 5 3 2 1)
(difflist->list
(mappend
(list->difflist '(4 5))
(reverse/difflist (list->difflist '(1 2 3)))))))
((eta 0.0) (memory #(6291456 1921152 1048576)) (stdout "") (stderr ""))
test/mlog: pass
Logging a value appends it as it is, as the list monoid does, so a writer logs the same over both.(define (test/mlog _)
(⊦= '(a) (difflist->list (mlog 'a)))
(⊦= '(1 2)
(difflist->list
(W:category-writer-log (W:>>= (W:tell 1) (λ (_) (W:tell 2)))))))
((eta 0.0) (memory #(6291456 1922528 1048576)) (stdout "") (stderr ""))
test/printer: pass
A difflist prints its content, left to right.(define (test/printer _)
(⊦= "⸨ 1 2 3 ⸩"
(with-output-to-string (λ () (display (list->difflist '(1 2 3))))))
(⊦= "⸨ ⸩" (with-output-to-string (λ () (display mempty)))))
((eta 0.0) (memory #(6291456 1923840 1048576)) (stdout "") (stderr ""))
(aux category state): the state monad
Introduction
This section tests the state monad, Haskell's State s a, where a computation is a transition from a state to the pair of its result and the next state, Haskell's (a, s) tuple; binding threads the state from one computation to the next, strictly, as Haskell's Control.Monad.State.Strict does.
Implementation
A computation is a category-state record around its transition, the image of Haskell's newtype, so that a computation is a type of its own and not any procedure; run-state applies the transition to a state.
(module
(aux category state)
*
(import scheme (chicken base) (aux base))
(define-record category-state run)
(set-record-printer! category-state (λ (m port) (display "#<state>" port)))
(define (run-state m s) ((category-state-run m) s))
(define (eval-state m s) (car (run-state m s)))
(define (exec-state m s) (cdr (run-state m s)))
(define (return v) (make-category-state (λ (s) (cons v s))))
(define (>>= m f)
(make-category-state
(λ (s) (let1 (r (run-state m s)) (run-state (f (car r)) (cdr r))))))
(define (fail . args)
(make-category-state
(λ (s)
(error "State monad does not support failure"
`((on ,s) (args unquote args))))))
(define get (make-category-state (λ (s) (cons s s))))
(define (put s) (make-category-state (λ (_) (cons (void) s))))
(define (modify f) (make-category-state (λ (s) (cons (void) (f s)))))
(define (gets f) (make-category-state (λ (s) (cons (f s) s))))
(define (state f) (make-category-state f))
(define (with-state f m) (make-category-state (λ (s) (run-state m (f s))))))
test/return: pass
return answers its value and leaves the state as it is.(define (test/return _)
(⊦= '(4 . s) (run-state (return 4) 's))
(⊦= '(4 . s) (run-state (do/monad ,4) 's)))
((eta 0.0) (memory #(12582912 2230392 1048576)) (stdout "") (stderr ""))
test/get-put: pass
get answers the state, put replaces it, and a later get sees the new one; with-state transforms the state before running a computation.(define (test/get-put _)
(⊦= '(3 . 3) (run-state get 3))
(⊦= (cons (void) 7) (run-state (put 7) 3))
(⊦= '((3 . 7) . 7)
(run-state (do/monad (← a get) (put 7) (← b get) ,(cons a b)) 3))
(⊦= '(10 . 10) (run-state (with-state (λ (s) (* 10 s)) get) 1)))
((eta 0.0) (memory #(12582912 2232216 1048576)) (stdout "") (stderr ""))
test/modify-gets: pass
modify applies a function to the state, gets answers a function of it.(define (test/modify-gets _)
(⊦= (cons (void) 6) (run-state (modify (λ (s) (* 2 s))) 3))
(⊦= '(4 . 3) (run-state (gets add1) 3))
(⊦= '((a b) b)
(run-state
(do/monad
(modify cdr)
(← x (gets car))
(put (list x))
(gets (λ (s) (cons 'a s))))
'(z b c))))
((eta 0.0) (memory #(12582912 2233712 1048576)) (stdout "") (stderr ""))
test/state: pass
state embeds a transition that answers the pair of the result and the next state.(define (test/state _)
(⊦= '(6 . 4) (run-state (state (λ (s) (cons (* 2 s) (add1 s)))) 3)))
((eta 0.0) (memory #(12582912 2235136 1048576)) (stdout "") (stderr ""))
test/run-eval-exec: pass
run-state answers the pair of the result and the final state, eval-state the result only and exec-state the final state only, as Haskell's runState, evalState and execState.(define (test/run-eval-exec _)
(⊦= '(0 . 1) (run-state tick 0))
(⊦= 0 (eval-state tick 0))
(⊦= 1 (exec-state tick 0))
(⊦= 10 (eval-state (with-state (λ (s) (* 10 s)) tick) 1))
(⊦= 11 (exec-state (with-state (λ (s) (* 10 s)) tick) 1)))
((eta 0.0) (memory #(12582912 2236944 1048576)) (stdout "") (stderr ""))
test/laws: pass
The left identity, right identity and associativity laws hold on every state they are run on.(define (test/laws _)
(let ((f (λ (x) (do/monad (modify (λ (s) (cons x s))) ,(* 2 x))))
(g (λ (y) (do/monad (← s get) (put (list y s)) ,(+ y 1))))
(m (do/monad (← s get) (put (cons 'm s)) ,(length s))))
(for-each
(λ (s0)
(⊦= (run-state (f 5) s0) (run-state (>>= (return 5) f) s0))
(⊦= (run-state m s0) (run-state (>>= m return) s0))
(⊦= (run-state (>>= (>>= m f) g) s0)
(run-state (>>= m (λ (x) (>>= (f x) g))) s0)))
'(() (a) (a b c)))))
((eta 0.0) (memory #(12582912 2231432 1048576)) (stdout "") (stderr ""))
test/order: pass
The clauses of a do/monad block run top to bottom, each on the state the previous one left.(define (test/order _)
(⊦= '((0 1 2) . 3)
(run-state (do/monad (← a tick) (← b tick) (← c tick) ,(list a b c)) 0))
(⊦= '(c b a)
(exec-state
(do/monad
(modify (cut cons 'a <>))
(modify (cut cons 'b <>))
(modify (cut cons 'c <>)))
'())))
((eta 0.0) (memory #(12582912 2232976 1048576)) (stdout "") (stderr ""))
test/when-unless: pass
when/monad and unless/monad run a computation only when a condition holds, or fails, as Haskell's when and unless.(define (test/when-unless _)
(⊦= 1 (exec-state (when/monad #t tick) 0))
(⊦= 0 (exec-state (when/monad #f tick) 0))
(⊦= 0 (exec-state (unless/monad #t tick) 0))
(⊦= 1 (exec-state (unless/monad #f tick) 0)))
((eta 0.0) (memory #(12582912 2234352 1048576)) (stdout "") (stderr ""))
test/applicative: pass
<$>, <*> and lift/monad run their computations left to right too, so in (<*> mf mx) the effects of mf come first.(define (test/applicative _)
(⊦= '(10 . 2) (run-state (<$> (λ (n) (* 10 n)) tick) 1))
(⊦= '(1 . 2) (run-state (<*> (do/monad (← n tick) ,(λ (m) (- m n))) tick) 0))
(⊦= '((0 . 1) . 2) (run-state (lift/monad cons tick tick) 0))
(⊦= '((0 . 1) . 2) (run-state (zip/monad tick tick) 0)))
((eta 0.0) (memory #(12582912 2236296 1048576)) (stdout "") (stderr ""))
test/strict: pass
Building a computation runs nothing; running (>>= m f) runs m before f is applied to its result, as the strict state monad does.(define (test/strict _)
(let* ((log '())
(m (do/monad
(← a (state (λ (s) (set! log (cons 'm log)) (cons 1 s))))
(let _ (set! log (cons 'f log)))
,a)))
(⊦= '() log)
(⊦= '(1 . s) (run-state m 's))
(⊦= '(f m) log)))
((eta 0.0) (memory #(12582912 2238264 1048576)) (stdout "") (stderr ""))
test/rerun: pass
A computation is a value: it may be run again, on any state, and it answers the same on the same state.(define (test/rerun _)
(let ((m (traverse/monad (λ (x) (do/monad (← n tick) ,(* n x))) '(1 2 3))))
(⊦= '((0 2 6) . 3) (run-state m 0))
(⊦= '((10 22 36) . 13) (run-state m 10))
(⊦= '((0 2 6) . 3) (run-state m 0))))
((eta 0.0) (memory #(12582912 2239440 1048576)) (stdout "") (stderr ""))
test/fail: pass
A value that does not match the pattern of a ← clause goes to fail, which raises an error when the computation runs, as Haskell's fail msg = error msg does, and not when it is built.(define (test/fail _)
(let* ((m (do/monad (← (,a unquote b) ,'()) ,a))
(message
(condition-case
(begin (run-state m 's) #f)
(e (exn) ((condition-property-accessor 'exn 'message) e)))))
(⊨ (category-state? m))
(⊨ (and (string? message)
(substring-index "State monad does not support failure" message)
#t))
(⊨ (category-state? (fail 'x)))
(⊦raises (exn) (run-state (fail 'x) 's))))
((eta 0.0) (memory #(12582912 2247672 1048576)) (stdout "") (stderr ""))
test/long-chain: pass
Chains of a hundred thousand binds run, nested to the right as the traversals build them and to the left as a fold over >>= does.(define (test/long-chain _)
(let* ((n 100000)
(sum (quotient (* n (- n 1)) 2))
(left (fold (λ (i m) (>>= m (λ (_) (modify (λ (s) (+ s i))))))
(return (void))
(iota n))))
(⊦= sum
(exec-state
(for-each/monad (λ (i) (modify (λ (s) (+ s i)))) (iota n))
0))
(⊦= sum (exec-state left 0))
(⊦= n (length (eval-state (replicate/monad n tick) 0)))))
((eta 0.393) (memory #(50331648 4642608 1048576)) (stdout "") (stderr ""))
test/representation: pass
A computation is a category-state record, as Haskell's newtype makes State s a a type of its own: a procedure, even one shaped as a transition, is not a computation. run-state does not check its argument, so running a value that is not a computation raises CHICKEN's own error for a record of the wrong type, the cheapest outcome.(define (test/representation _)
(⊨ (category-state? (do/monad (← x get) (return x))))
(⊨ (not (category-state? (λ (s) (cons 1 s)))))
(⊦raises (exn type) (run-state 42 0)))
((eta 0.0) (memory #(50331648 4648312 1048576)) (stdout "") (stderr ""))
test/traversals: pass
The traversals run their computations left to right, the head first, so effects on the state happen in list order, as Haskell's mapM, sequence, replicateM, mapM_ and foldM do.(define (test/traversals _)
(⊦= '(((a . 0) (b . 1) (c . 2)) . 3)
(run-state
(traverse/monad (λ (x) (<$> (cut cons x <>) tick)) '(a b c))
0))
(⊦= '(() . s) (run-state (traverse/monad (λ (x) tick) '()) 's))
(⊦= '((0 1 2) . 3) (run-state (replicate/monad 3 tick) 0))
(⊦= '(() . 0) (run-state (replicate/monad 0 tick) 0))
(⊦= (cons (list 0 (void) 10) 11)
(run-state
(sequence/monad (list tick (modify (λ (s) (* 10 s))) tick))
0))
(⊦= '(c b a)
(exec-state
(for-each/monad (λ (x) (modify (cut cons x <>))) '(a b c))
'()))
(⊦= '(((1 b) (0 a)) . 2)
(run-state
(fold/monad
(λ (acc x) (do/monad (← n tick) ,(cons (list n x) acc)))
'()
'(a b))
0)))
((eta 0.0) (memory #(50331648 4646536 1048576)) (stdout "") (stderr ""))
test/list->monad-is-not-sequence: pass
list->monad is return of the mconcat of a pure list, not Haskell's sequence: given a list of computations it answers that very list, unrun, and leaves the state as it is. To run a list of computations, use sequence/monad.(define (test/list->monad-is-not-sequence _)
(⊦= '((1 2) . 0) (run-state (list->monad '(1 2)) 0))
(⊦= '(1 2) (eval-state (list->monad '(1 2)) 0))
(let1 (r (run-state (list->monad (list tick tick)) 0))
(⊦= 0 (cdr r))
(⊨ (and (= 2 (length (car r))) (every category-state? (car r)))))
(⊦= '((0 1) . 2) (run-state (sequence/monad (list tick tick)) 0)))
((eta 0.0) (memory #(50331648 4648168 1048576)) (stdout "") (stderr ""))
test/label: pass
The usage example of the module numbers the leaves of a tree left to right: the state is the next number, and the left subtree's effects come before the right one's.(define (test/label _)
(⊦= '((((0 . a) 1 . b) 2 . c) . 3) (run-state (label '((a . b) . c)) 0)))
((eta 0.0) (memory #(50331648 4650656 1048576)) (stdout "") (stderr ""))