Skip to main content

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.

Metadata

Release files for hymn 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hymn 1.1.0
File Size Uploaded Provenance
hymn-1.1.0.tar.gz 25.8 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for hymn 1.1.0
File Interpreter ABI Platform
hymn-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 47.2 kB

Release files / hymn-1.1.0.tar.gz

Download URL hymn-1.1.0.tar.gz
Size 25.8 kB
Tags Source
SHA-256 checksum
How to use checksums
854e6e2db7490e80423c68b0248bd462679a8d38821dd39c2bd4202c97081f6d
BLAKE2b-256 checksum
How to use checksums
8e57aa21e9d6bd3435886486a6d47635291c465d0df071d37c444b340b1fdc68
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.11.2 CPython/3.11.7

Release files / hymn-1.1.0-py3-none-any.whl

Download URL hymn-1.1.0-py3-none-any.whl
Size 21.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9943f332b3661ca03de078d64beec0de9166a03c9fc23344fae848262d50678a
BLAKE2b-256 checksum
How to use checksums
527378245384eb3bb60c3e30b7ca3108e3d6b7a8ba9ee104ced6d0361581a0d5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.11.2 CPython/3.11.7

Security for hymn 1.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page