Skip to main content

Hy Monad Notation - a monad library for Hy

Project description

Introduction

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

Code are better than words.

The continuation monad

=> (import hymn.dsl [cont-m call/cc])
=> ;; computations in continuation passing style
=> (defn double [x] (cont-m.unit (* x 2)))
=> (setv length (cont-m.monadic len))
=> ;; chain with bind
=> (.run (>> (cont-m.unit [1 2 3]) length double))
6
=> (defn square [n] (call/cc (fn [k] (k (** n 2)))))
=> (.run (square 12))
144
=> (.run (square 12) (fn [x] (+ x 1)))
145
=> (.run (square 12) str)
"144"
=> (require hymn.dsl [do-monad-return])
=> (.run (do-monad-return [sqr (square 42)] (.format "answer^2 = {}" sqr)))
"answer^2 = 1764"

The either monad

=> (import hymn.dsl [Left Right either failsafe])
=> (require hymn.dsl [do-monad-return])
=> ;; do notation with either monad
=> (do-monad-return [a (Right 1) b (Right 2)] (/ a b))
Right(0.5)
=> (do-monad-return [a (Right 1) b (Left NaN)] (/ a b))
Left(nan)
=> ;; failsafe is a function decorator that wraps return value into either
=> (import hy.pyops [/])
=> (setv 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 calls functions accordingly
=> (either print (fn [x] (+ x 1)) (safe-div 4 2))
3.0
=> (either print (fn [x] (+ x 1)) (safe-div 1 0))
division by zero

The identity monad

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

The lazy monad

=> (import hymn.dsl [force])
=> (require hymn.dsl [lazy])
=> ;; lazy computation implemented as monad
=> ;; macro lazy creates deferred computation
=> (setv a (lazy (print "evaluate a") 42))
=> ;; the computation is deferred, notice the value is shown as '_'
=> a
Lazy(_)
=> ;; evaluate it
=> (.evaluate a)
evaluate a
42
=> ;; now the value is cached
=> a
Lazy(42)
=> ;; calling evaluate again will not trigger the computation
=> (.evaluate a)
42
=> (setv b (lazy (print "evaluate b") 21))
=> b
Lazy(_)
=> ;; force evaluate the computation, same as calling .evaluate on the monad
=> (force b)
evaluate b
21
=> ;; force on values other than lazy return the value unchanged
=> (force 42)
42
=> (require hymn.dsl [do-monad-return])
=> ;; do notation with lazy monad
=> (setv c (do-monad-return
...          [x (lazy (print "get x") 1)
...           y (lazy (print "get y") 2)]
...          (+ x y)))
=> ;; the computation is deferred
=> c
Lazy(_)
=> ;; do it!
=> (force c)
get x
get y
3
=> ;; again
=> (force c)
3

The list monad

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

The maybe monad

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

The reader monad

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

The state monad

=> (import hymn.dsl [lookup-state set-value])
=> (require hymn.dsl [do-monad-return])
=> ;; do notation with state monad,
=> ;; set-value sets the value with key in the state
=> (setv s (do-monad-return [x (lookup-state "a") _ (set-value "b" (+ x 1))] x))
=> ;; run state monad s with initial state
=> (.run s {"a" 1})
#(1 {"a" 1  "b" 2})

The writer monad

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

Operations on monads

=> (import hymn.dsl [lift])
=> ;; lift promotes function into monad
=> (import hy.pyops [+])
=> (setv m+ (lift +))
=> ;; lifted function can work on any monad
=> ;; on the maybe monad
=> (import hymn.dsl [Just Nothing])
=> (m+ (Just 1) (Just 2))
Just(3)
=> (m+ (Just 1) Nothing)
Nothing
=> ;; on the either monad
=> (import hymn.dsl [Left Right])
=> (m+ (Right 1) (Right 2))
Right(3)
=> (m+ (Left 1) (Right 2))
Left(1)
=> ;; on the list monad
=> (import hymn.dsl [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
=> (import functools [reduce])
=> (reduce m+ [(Just 1) (Just 2) (Just 3)])
Just(6)
=> (reduce m+ [(Just 1) Nothing (Just 3)])
Nothing
=> ;; <-r is an alias of lookup-reader
=> (import hymn.dsl [<-r])
=> (require hymn.dsl :readers [^])
=> ;; ^ is the reader macro for lift
=> (setv p (#^ print (<-r 'message) :end (<-r 'end)))
=> (.run p {'message "Hello world" 'end "!\n"})
Hello world!
=> ;; pseudo random number - linear congruential generator
=> (import hymn.dsl [state-m get-state set-state])
=> (setv random
...      (>> get-state
...          (fn [s] (state-m.unit (% (+ (* s 69069) 1) (** 2 32))))
...          set-state))
=> (.run random 1234)
#(1234 85231147)
=> ;; random can be even shorter by using modify
=> (import hymn.dsl [modify])
=> (setv random (modify (fn [s] (% (+ (* s 69069) 1) (** 2 32)))))
=> (.run random 1234)
#(1234 85231147)
=> ;; use replicate to do computation repeatly
=> (import hymn.dsl [replicate])
=> (.evaluate (replicate 5 random) 42)
[42 2900899 2793697416 2186085609 1171637142]
=> ;; sequence on writer monad
=> (import hymn.dsl [sequence])
=> (import hymn.dsl [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.0, 2)
Right(0.5)
>>> safe_div(1, 0)
Left(ZeroDivisionError(...))

Requirements

  • hy >= 0.27.0

For hy version 0.19, please install hymn 0.9

For hy version 0.14, please install hymn 0.8

For hy version 0.13, please install hymn 0.7.

For hy version 0.12, please install hymn 0.6.

For hy version 0.11 and earlier, please install hymn 0.5.

See Changelog section in documentation for details.

Installation

Install from PyPI:

pip install hymn

License

BSD New, see LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hymn-1.1.0.tar.gz (25.8 kB view details)

Uploaded Source

Built Distribution

hymn-1.1.0-py3-none-any.whl (21.4 kB view details)

Uploaded Python 3

File details

Details for the file hymn-1.1.0.tar.gz.

File metadata

  • Download URL: hymn-1.1.0.tar.gz
  • Upload date:
  • Size: 25.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.11.2 CPython/3.11.7

File hashes

Hashes for hymn-1.1.0.tar.gz
Algorithm Hash digest
SHA256 854e6e2db7490e80423c68b0248bd462679a8d38821dd39c2bd4202c97081f6d
MD5 b256e0cf1b9206ab7b87755dad750834
BLAKE2b-256 8e57aa21e9d6bd3435886486a6d47635291c465d0df071d37c444b340b1fdc68

See more details on using hashes here.

File details

Details for the file hymn-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: hymn-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 21.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.11.2 CPython/3.11.7

File hashes

Hashes for hymn-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9943f332b3661ca03de078d64beec0de9166a03c9fc23344fae848262d50678a
MD5 80723d2ac73ae622521f3855f60199f5
BLAKE2b-256 527378245384eb3bb60c3e30b7ca3108e3d6b7a8ba9ee104ced6d0361581a0d5

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page