Hy Monad Notation - a monad library for Hy

Introduction

Hymn is a monad library for Hy/Python, with do notation for monad comprehension.

Code are better than words.

The continuation monad

=> (require hymn.dsl)
=> (import [hymn.types.continuation [cont-m call-cc]])
=> ;; computations in continuation passing style
=> (defn double [x] (cont-m.unit (* x 2)))
=> (def length (cont-m.monadic len))
=> ;; chain with bind
=> (.run (>> (cont-m.unit [1 2 3]) length double))
6

The either monad

=> (require hymn.dsl)
=> (import [hymn.types.either [Left Right either failsafe]])
=> ;; do notation with either monad
=> (do-monad [a (Right 1) b (Right 2)] (/ a b))
Right(0.5)
=> ;; Left value is mzero
=> (do-monad [a (Right 1) b (Left 'nan)] (/ a b))
Left(nan)
=> ;; failsafe is a function decorator that wraps return value into either
=> (def safe-div (failsafe /))
=> ;; returns Right if nothing wrong
=> (safe-div 4 2)
Right(2.0)
=> ;; returns Left when bad thing happened, like exception being thrown
=> (safe-div 1 0)
Left(ZeroDivisionError('division by zero',))
=> ;; function either tests the value and call functions accordingly
=> (either print inc (safe-div 4 2))
3.0
=> (either print inc (safe-div 1 0))
division by zero

The identity monad

=> (require hymn.dsl)
=> (import [hymn.types.identity [identity-m]])
=> ;; do notation with identity monad is like let binding
=> (do-monad [a (identity-m 1) b (identity-m 2)] (+ a b))
Identity(3)

The list monad

=> (require hymn.dsl)
=> (import [hymn.types.list [list-m]])
=> ;; use list-m contructor to turn sequence into list monad
=> (def xs (list-m (range 2)))
=> (def ys (list-m (range 3)))
=> ;; do notation with list monad is list comprehension
=> (list (do-monad [x xs y ys :when (not (zero? y))] (/ x y)) )
[0.0, 0.0, 1.0, 0.5]
=> ;; * is the reader macro for list-m
=> (list (do-monad [x #*(range 2) y #*(range 3) :when (not (zero? y))] (/ x y)) )
[0.0, 0.0, 1.0, 0.5]

The maybe monad

=> (require hymn.dsl)
=> (import [hymn.types.maybe [Just Nothing maybe]])
=> ;; do notation with maybe monad
=> (do-monad [a (Just 1) b (Just 1)] (/ a b))
Just(1.0)
=> ;; Nothing yields Nothing
=> (do-monad [a Nothing b (Just 1)] (/ a b))
Nothing
=> ;; maybe is a function decorator the wraps return value into maybe
=> ;; a safe-div with maybe monad
=> (def safe-div (maybe /))
=> (safe-div 42 42)
Just(1.0)
=> (safe-div 42 'answer)
Nothing
=> (safe-div 42 0)
Nothing

The reader monad

=> (require hymn.dsl)
=> (import [hymn.types.reader [lookup]])
=> ;; do notation with reader monad, lookup assumes the environment is subscriptable
=> (def r (do-monad [a (lookup 'a) b (lookup 'b)] (+ a b)))
=> ;; run reader monad r with environment
=> (.run r {'a 1 'b 2})
3

The state monad

=> (require hymn.dsl)
=> (import [hymn.types.state [lookup set-value]])
=> ;; do notation with state monad, set-value sets the value with key in the state
=> (def s (do-monad [a (lookup 'a) _ (set-value 'b (inc a))] a))
=> ;; run state monad s with initial state
=> (.run s {'a 1})
(, 1 {'a 1 'b 2})

The writer monad

=> (import [hymn.types.writer [tell]])
=> ;; do notation with writer monad
=> (do-monad [_ (tell "hello") _ (tell " world")] nil)
StrWriter((None, 'hello world'))
=> ;; int is monoid, too
=> (.execute (do-monad [_ (tell 1) _ (tell 2) _ (tell 3)] nil))
6

Operations on monads

=> (require hymn.dsl)
=> (import [hymn.operations [lift]])
=> ;; lift promotes function into monad
=> (def m+ (lift +))
=> ;; lifted function can work on any monad
=> ;; on the maybe monad
=> (import [hymn.types.maybe [Just Nothing]])
=> (m+ (Just 1) (Just 2))
Just(3)
=> (m+ (Just 1) Nothing)
Nothing
=> ;; on the either monad
=> (import [hymn.types.either [Left Right]])
=> (m+ (Right 1) (Right 2))
Right(3)
=> (m+ (Left 1) (Right 2))
Left(1)
=> ;; on the list monad
=> (import [hymn.types.list [list-m]])
=> (list (m+ (list-m "ab") (list-m "123")))
['a1', 'a2', 'a3', 'b1', 'b2', 'b3']
=> (list (m+ (list-m "+-") (list-m "123") (list-m "xy")))
['+1x', '+1y', '+2x', '+2y', '+3x', '+3y', '-1x', '-1y', '-2x', '-2y', '-3x', '-3y']
=> ;; can be used as normal function
=> (reduce m+ [(Just 1) (Just 2) (Just 3)])
Just(6)
=> (reduce m+ [(Just 1) Nothing (Just 3)])
Nothing
=> ;; <- is an alias of lookup
=> (import [hymn.types.reader [<-]])
=> ;; ^ is the reader macro for lift
=> (def p (#^print (<- 'message) :end (<- 'end)))
=> (.run p {'message "Hello world" 'end "!\n"})
Hello world!
=> ;; random number - linear congruential generator
=> (import [hymn.types.state [get-state set-state]])
=> (def random (>> get-state (fn [s] (-> s (* 69069) inc (% (** 2 32)) set-state))))
=> (.run random 1234)
(1234, 85231147)
=> (import [hymn.operations [replicate]])
=> (.evaluate (replicate 5 random) 42)
[42, 2900899, 2793697416, 2186085609, 1171637142]
=> ;; sequence on writer monad
=> (import [hymn.operations [sequence]])
=> (import [hymn.types.writer [tell]])
=> (.execute (sequence (map tell (range 1 101))))
5050

Using Hymn in Python

>>> from hymn.dsl import *
>>> sequence(map(tell, range(1, 101))).execute()
5050
>>> msum = lift(sum)
>>> msum(sequence(map(maybe(int), "12345")))
Just(15)
>>> msum(sequence(map(maybe(int), "12345a")))
Nothing
>>> @failsafe
... def safe_div(a, b):
...     return a / b
...
>>> safe_div(1, 2)
Right(0.5)
>>> safe_div(1, 0)
Left(ZeroDivisionError('division by zero',))

Requirements

  • hy >= 0.11.0

Installation

Install from PyPI:

pip install hymn

Install from source, download source package, decompress, then cd into source directory, run:

make install

License

BSD New, see LICENSE for details.