-
Notifications
You must be signed in to change notification settings - Fork 0
03 The prelude
Sibilisp ships with a Fantasy-Land inspired functional programming toolkit, called the prelude. You can use functions and structures defined in the prelude by importing them directly from sibilisp/prelude
via Sibilisp's (use)
.
(use "sibilisp/prelude" identity map)
- Logic functions
- Type checking
- Function-manipulating functions
- Generic operations
- List and MSet operations
- Lenses
- Algebraic types
- Algebraic type coercion
Basic functions from combinatorial logic.
Signature
identity :: a -> a
The identity function returns the given argument.
(identity a) ; => a
Signature
constantly :: a -> () -> a
The constantly function takes a value and returns a constant function that always returns the value.
(constantly a) ; => (() -> a)
Helper functions to check types and collections.
Signature
list-of? :: List L => (L a (a -> Boolean)) -> Boolean
Takes a list and a predicate function and tests if each element in the list passes the predicate. Returns a boolean.
(list-of? ls (#-> (string?))) ; => Boolean
Signature
hash-of? :: Hash H => (H a (a -> Boolean)) -> Boolean
Takes a hash and a predicate function and tests if each element in the hash passes the predicate. Returns a boolean.
(hash-of? hs (#-> (string?))) ; => Boolean
Signature
dict-of? :: Dict D => (D a (a -> Boolean)) -> Boolean
Takes a dict and a predicate function and tests if each element in the dict passes the predicate. Returns a boolean.
(dict-of? dt (#-> (string?))) ; => Boolean
Signature
mset-of? :: MSet M => (M a (a -> Boolean)) -> Boolean
Takes a mset and a predicate function and tests if each element in the mset passes the predicate. Returns a boolean.
(mset-of? ms (#-> (string?))) ; => Boolean
Some higher-order-functions
Signature
converge :: ((&rest a) -> b), (list &rest (c -> a)) -> (&rest c) -> b
Takes a multary function and a list of unary functions and returns a unary function, that passes the given argument through the list of unary functions and passes the resulting values as arguments to the multary function.
(defun greet (first-name last-name)
(+ "Hello " first-name " " last-name))
(call
(converge greet (list (#-> (getf 'first))
(#-> (getf 'last))))
(hash :first "John"
:last "Doe")) ; => "Hello, John Doe"
Signature
memoize :: ((&rest a) -> b) -> (&rest a) -> b
Takes an n-ary function and returns a function that caches and returns the results of calls with equal arguments.
(defun compute (a b)
(.log console (+ "Got: " a " & " b)) ; side effect for demonstation
(+ a b))
(defconstant mcompute (memoize compute))
(mcompute 1 2) ; => 3, "Got 1 & 2"
(mcompute 1 2) ; => 3
Helper functions for working with differnent kinds of structures.
Note
The term 'unknown type' refers to types that are not part of the runtime. In other words, all of the prelude's algebraic types as well as types that are defined by the programmer fall into this category.
Signature
show :: a -> String
Takes a value of any type and returns a string representation of it. Expands the inner fields of structures (if any) into a recursive call to show. If an unknown type is given that implements a to-string
method, this is called. Otherwise for unknown types, the constructor's name is used.
(show 1) ; => "(number 1)"
(show "a string") ; => "(string "a string")"
(show (hash :a 1)) ; => "(hash :a (number 1))"
(show (coyo.of 1)) ; => "(coyo (number 1) (function))"
;; --- unknown type
(deftype foo (x))
(show (foo 1)) ; => "(foo)"
Signature
equals :: Setoid S => (S a, S a) -> Boolean
Takes two values of any type and compares them for deep structural and value equality. This means that for example a list with the numbers 1, 2, and 3, is considered equal to itself as well as to any list that has the same length
and contains the same numbers. If two unknown types are given and the first type implements an equals
method, it is used for comparison. If the type is unknown and no equals method is implemented, false
is returned.
(equals 1 1) ; => true
(equals "a" "a") ; => true
(equals "a" "A") ; => false
(let ((a (list 1 2 3))
(b (list 1 2 3)))
(equals a b)) ; => true
;; --- unknown type
(deftype foo (x))
(defmethod foo equals (a)
(and (.is foo a)
(eql? (getf this 'x)
(getf a 'x))))
(let ((a (foo 1))
(b (foo 1)))
(equals a b)) ; => true
Signature
concatenate :: Semigroup S => (S a, S a) -> S a
Takes two values of any type that belong to the same Semigroup
(string, function, list, mset, hash, dict, future, unknown) and concatenates them together. If unknown types are given and the first type implements a concat
method, it is used for concatenation. If a type does not belong to a semigroup, an error is thrown.
(concatenate "a" "b") ; => "ab"
(concatenate (list 1) (list 2 3)) ; => (list 1 2 3)
;; --- unknown type
(deftype sum (x))
(defmethod sum concat (a)
(if (.is sum a)
(sum (+ (getf this 'x)
(getf a 'x)))))
(concatenate (sum 1) (sum 2)) ; => (sum 3)
Signature
map :: Functor F => (F a, (a -> b)) -> F b
Takes a value of any type that belongs to the Functor
types (function, list, mset, hash, dict, future, unknown) and a function in the shape (a -> b)
and projects the function over the value in the functor. If an unknown type is given and it implements a map
method, it is used for projecting the function over the value. If a type is not a functor, an error is thrown.
(let ((a (list 1 2 3))
(f (#-> (+ 1))))
(map a f)) ; => (list 2 3 4)
(let ((a (#-> (.split "")))
(b (#-> (.reverse))))
(map a b)) ; => (lambda (x) (.reverse (.split x "")))
;; --- unknown type
(deftype foo (x))
(defmethod foo map (f)
(foo (f (getf this 'x))))
(map (foo 1) (#-> (+ 1))) ; => (foo 2)
Signature
ap :: Functor F, Applicative A => (F a, A (a -> b)) -> F b
Takes two values of any type that belong to the Functor
types (function, list, mset, hash, dict, future, unknown) and the Applicative
types (function, list, mset, hash, dict, future, unknown) and applies the functions in the applicative to the values in the functor. If unknown types are given and the first and the first type implements a ap
method, it is used for application. If the first type is not a functor, an error is thrown.
(defun add (n) (#-> (+ n)))
(defun mul (n) (#-> (* n)))
(let ((f (list (add 1) (mul 2))))
(ap (list 1 2 3) f)) ; => (list 4 6 8)
(let ((f (hash :a (add 1) :b (mul 2))))
(ap (hash :a 1 :b 2 :c 3) f)) ; => (hash :a 2 :b 4 :c 3)
;; --- unknown type
(deftype foo (x))
(defmethod foo map (f)
(foo (f (getf this 'x))))
(defmethod foo ap (f)
(foo (call (getf this 'x) (getf f 'x))))
(ap (foo 1) (foo (#-> (+ 1)))) ; => (foo 2)
Signature
flat-map :: (M a, (a -> M b)) -> M b
chain :: (M a, (a -> M b)) -> M b
Takes a value of any type that belongs to the Monad
types (function, list, future, unknown) and a function in the shape Monad M. (a -> M b)
and projects the function over the value in the monad. If an unknown type is given and it implements a flat-map
or chain
method, it is used for projecting the function over the value. If a type is not a monad, an error is thrown.
(let ((a (list 1 2 3))
(f (#-> (+ 1) (list))))
(chain a f)) ; => (list 2 3 4)
;; --- unknown type
(deftype foo (x))
(defmethod foo flat-map (f)
(f (getf this 'x)))
(chain (foo 1) (#-> (+ 1) (foo))) ; => (foo 2)
Signature
contramap :: Contravariant C => (C a, (a -> b)) -> C b
Takes a value of any type that belongs to the Contravariant Functor
types (list, mset, future, unknown) and a function in the shape (a -> b)
and projects the function over the value in the contravariant functor. If an unknown type is given and it implements a contramap
method, it is used for projecting the function over the value. If a type is not a contravariant functor, an error is thrown.
(let ((a (list))
(f (#> 1)))
(contramap a f)) ; => (list 1)
;; --- unknown type
(deftype foo (x))
(defmethod foo contramap (f)
(ternary (not (mod (getf this 'x) 3))
(foo (f))
(foo (getf this 'x))))
(contramap (foo 1) (#> 2)) ; => (foo 1)
(contramap (foo 9) (#> 2)) ; => (foo 2)
Signature
bimap :: Bifunctor B => (B a c, (a -> b), (c -> d)) -> B b d
Takes a value of any type that belongs to the Bifunctor
types (list, mset, future, unknown) and two functions of which the first one has the shape (() -> a)
and the second one has the shape (a -> b)
. Projects the first function if the type is empty (contravariant) and the second one if the type is not empty (functor). If an unknown type is given and it implements a .bimap
method, it is used for projection. If a type is not a bifunctor, an error is thrown.
(let ((a (list))
(f (#> 1))
(g (#> #0)))
(bimap a f g)) ; => (list 1)
(let ((a (list 2))
(f (#> 1))
(g (#> #0)))
(bimap a f g)) ; => (list 2)
;; --- unknown type
(deftype foo (x))
(defmethod foo bimap (f g)
(ternary (mod (getf this 'x) 3)
(foo (f (getf this 'x)))
(foo (g))))
(bimap (foo 1) (#> 3) (#> #0)) ; => (foo 1)
(bimap (foo 9) (#> 3) (#> #0)) ; => (foo 3)
Signature
promap :: Profunctor P => (P (b -> c), (a -> b), (c -> d)) -> P (a -> d)
Takes a value of any type that belongs to the Profunctor
types (function, unknown) and two functions of which the first one has the shape (a -> b)
and the second one has the shape (c -> d)
, returning a profunctor that contains the projection from the first function into the profunctor and then the result into the second function. If an unknown type is given and it implements a .promap
method, it is used for projection. If a type is not a profunctor, an error is thrown.
(let ((f (#> 3))
(g (#-> (* 3)))
(h (#-> (+ 1))))
(promap g f h)) ; (lambda () (+ 1 (* 3 3)))
;; --- unknown type
(deftype foo (x))
(defmethod foo promap (f g)
(let ((x (getf this 'x)))
(foo (#-> (f) (x) (g)))))
(bimap (foo (#-> (+ ", ")))
(#> "Hello")
(#-> (+ "World"))) ; => (foo (lambda () "Hello, World"))
Signature
reduce :: Foldable F => (F a, ((b, a) -> b), b) -> b
Takes a value of any type that belongs to the Foldable
types (list, mset, unknown), a function of the shape ((a, b) -> a)
and a seed value that becomes a
. Uses the function to accumulate the value inside the foldable into the seed value. If an unknown type is given and it implements a .reduce
method, it is used for reduction. If a type is not a foldable, an error is thrown.
(let ((a (list 1 2 3))
(f (#(s n) (+ s n))))
(reduce a f 0)) ; => 6
;; --- unknown type
(deftype foo (x))
(defmethod foo reduce (f a)
(f a (getf this 'x)))
(reduce (foo 1)
(#(a b) (+ 2 a b))
3) ; => 6
Signature
fold-map :: Foldable F, Semigroup S => (F a, (a -> S b)) -> S b
Takes a value of any type that belongs to the Foldable
types (list, mset, unknown) and a function in the shape Semigroup S => (a -> S b)
. Uses the function to accumulate the value inside the foldable into a semigroup returned by the function. If an unknown type is given and it implements a .fold-map
method, it is used for reduction, otherwise .reduce
is used. If a type is not a foldable, an error is thrown.
(deftype sum (n))
(defmethod sum concat (s)
(sum (+ (getf s 'n) (getf this 'n))))
(fold-map (list 1 2 3) (#-> (+ 1) (sum))) ; => (sum 9)
Signature
fold :: Foldable F, Semigroup S => (F a, (a -> S b)) -> S b
Takes a value of any type that belongs to the Foldable
types (list, mset, unknown) and a function that lifts a value into a Semigroup. Accumulates the value inside the foldable into the semigroup. If an unknown type is given and it implements a .fold
method, it is used for reduction. If a type is not a foldable, an error is thrown.
(deftype sum (n))
(defmethod sum concat (s)
(sum (+ (getf s 'n) (getf this 'n))))
(fold (list 1 2 3) sum) ; => (sum 6)
Signature
traverse :: Traversable T, Applicative A => (T a, (b -> A b), (a -> A b)) -> A T b
Takes a value of any type that belongs to the Traversable
types (list, mset, unknown), a function that lifts a value into an Applicative and a transformer function of the shape Applicative A. (a -> A b)
. Traverses the traversable with the transformer function. If an unknown type is given and it implements a .traverse
method, it is used for traversal. If a type is not a traversable, an error is thrown.
(deftype foo (n))
(defmethod foo map (f)
(foo (f (getf this 'n))))
(defmethod foo ap (ff)
(foo (call (getf this 'n) (getf ff 'n))))
(traverse (list 1 2 3)
foo.lift
(#(n) (foo (+ n 1)))) ; => (foo (list 2 3 4))
Signature
sequence :: Traversable T, Applicative A => (T A a, (b -> A b)) -> A T b
Takes a value of any type that belongs to the Traversable
types (list, mset, unknown) and a function that lifts a value into an Applicative. Inverts the shape of the traversable and the values in it. If an unknown type is given and it implements a .sequence
method, it is used for traversal. If a type is not a traversable, an error is thrown.
(deftype foo (n))
(defmethod foo map (f)
(foo (f (getf this 'n))))
(defmethod foo ap (ff)
(foo (call (getf this 'n) (getf ff 'n))))
(sequence (list (foo 1) (foo 2) (foo 3))
foo) ; => (foo (list 1 2 3))
Signature
alt :: Alt A => (A a, A a) -> A a
Takes two values that belongs to the Alt
types (list, mset, unknown) of which the first is the value and the second is the fallback value. Returns the fallback value if the given value is empty. If an unknown type is given and it implements a .alt
method, it is used for selection. If a type is not an alt, an error is thrown.
(let ((a (list))
(fallback (list 2)))
(alt a fallback)) ; => (list 2)
(let ((a (list 1))
(fallback (list 2)))
(alt a fallback)) ; => (list 1)
;; --- unknown type
(deftype foo (x))
(defmethod foo alt (f)
(if (nothing? (getf this 'x))
f
this))
(alt (foo nil) (foo 2)) ; => (foo 2)
(alt (foo 1) (foo 2)) ; => (foo 1)
Signature
clone :: a -> a
Takes a value of any type that is clonable (string, number, function, boolean, nil, void, list, mset, hash, dict, date, regex, unknown) and returns clone of it. Primitive types are not cloned since they are compared by value, complex types are cloned recursively. If a non clonable type is given, an error is thrown.
(let ((a (list 1 2 3)))
(clone a)) ; => (list 1 2 3)
Functions that handle collections
Signature
zip :: List L => (L a, L b) -> L L c
zip :: Mset M, List L => (M a, M b) -> L L c
Takes two collections and packs them together into a list of lists of key-value pairs. Uses the contents of the first list for the keys and the contents of the second list as values. Only produces pairs up to the length of the shortest list.
(let ((a (list 1 2 3))
(b (list 'a 'b 'c)))
(zip a b)) ; => (list (list 1 'a) (list 2 'b) (list 3 'c))
Signature
unzip :: List L => L L a -> L L b
Signature
find :: List L, Maybe M => (L a, (a -> Boolean)) -> M a
find :: Mset L, Maybe M => (L a, (a -> Boolean)) -> M a
Takes a collection and a predicate function and returns a maybe.some
with the first item that satisfies the predicate or a maye.nothing
if no item is found.
(let ((a (list 1 2 3))
(f (#-> (< 2))))
(find a f)) ; => (maybe.some 1)
Signature
filter :: List L => (L a, (a -> Boolean)) -> L a
filter :: Mset M => (M a, (a -> Boolean)) -> M a
select :: List L => (L a, (a -> Boolean)) -> L a
select :: Mset M => (M a, (a -> Boolean)) -> M a
Takes a collection and a predicate function and returns a collection of the same type containing all items that pass the predicate function.
(let ((a (list 1 2 3))
(f (#-> (<= 2))))
(filter a f)) ; => (list 1 2)
Signature
reject :: List L => (L a, (a -> Boolean)) -> L a
reject :: Mset M => (M a, (a -> Boolean)) -> M a
The opposite of filter/select
, takes a collection and a predicate function and returns a collection of the same type containing all items the did not pass the predicate function.
(let ((a (list 1 2 3))
(f (#-> (<= 2))))
(reject a f)) ; => (list 3)
Signature
unique :: List L => L a -> L a
unique :: Mset M => M a -> M a
Takes a collection and returns a collection of the same type that contains only unique items. This function is mainly useful for lists, since msets usually only contain unique items by default.
(let ((a (list 1 2 2 3 1 1 2 3)))
(uniqe a)) ; => (list 1 2 3)
Signature
union :: List L => (L a, L a) -> L a
union :: Mset M => (M a, M a) -> M a
Takes two collections and returns a collection of the same type that is the union of both collections.
(let ((a (list 1 2 3))
(b (list 1 3 5)))
(union a b)) ; => (list 1 2 3 5)
Signature
intersection :: List L => (L a, L a) -> L a
intersection :: Mset M => (M a, M a) -> M a
Takes two collections and returns a collection of the same type that only contains items found in both collections.
(let ((a (list 1 2 3))
(b (list 1 3 5)))
(intersection a b)) ; => (list 1 3)
Signature
difference :: List L => (L a, L a) -> L a
difference :: Mset M => (M a, M a) -> M a
Takes two collections and returns a collection of the same type that only contains items found in either the first or second collection but not in both.
(let ((a (list 1 2 3))
(b (list 1 3 5)))
(difference a b)) ; => (list 2 5)
Signature
take :: List L => (L a, Number) -> L a
take :: Mset M => (M a, Number) -> M a
Takes a collection and a number and returns a collection of the same type containing as much items from the beginning of the given collection as the second argument indicates.
(let ((a (list 1 2 3)))
(take a 2)) ; => (list 1 2)
Signature
drop :: List L => (L a, Number) -> L a
drop :: Mset M => (M a, Number) -> M a
Takes a collection and a number and returns a collection of the same type where as many items from the beginning of the collection as the second argument indicates are dropped.
(let ((a (list 1 2 3)))
(drop a 2)) ; => (list 3)
Signature
partition :: List L => (L a, Number) -> L L a
partition :: Mset M => (M a, Number) -> M L a
Takes a collection and a number and returns a collection of the same type which itself contains lists with as many items inside as the second argument defines. If the (rest) collection cannot be partitioned according to the second argument, a shorter partition is created.
(let ((a (list 1 2 3 4 5)))
(partition a 3)) ; => (list (list 1 2 3) (list 4 5))
Signature
partition-with :: List L => (L a, (a -> Boolean)) -> L L a
partition-with :: Mset M => (M a, (a -> Boolean)) -> M L a
Works like partition
, but takes a predicate function and creates new partitions whenever the predicate changes it's return value.
(let ((a (list 1 2 3 4 5))
(f (#-> (mod 3) (eql? 0))))
(partition-with a f)) ; => (list (list 1 2) (list 3) (list 4 5))
Signature
keep :: List L => L a -> L a
keep :: Mset M => M a -> M a
Takes a collection and returns a collection of the same type with all nil
or void
items removed.
(let ((a (list 1 (nil) 3)))
(keep a)) ; => (list 1 3)
Van Laarhoven style lenses for hash and dict structures. Lenses are a non-destructive and pure way to work with data structures like hashes.
Signature
create-lens :: (((k, a k y) -> y), ((k, y, a k y) -> a k y)) -> k -> (a k y, (y -> z)) -> a k z
Takes a getter and a setter function and returns a lens. Useful to create new types of lenses for new structures (also see hash-lens
and dict-lens
).
Signature
hash-lens :: Hash H => (&rest k) -> (H k a, (a -> b)) -> H k b
Takes a variadic amount of "keys" and returns a lens for each "key" that works on hash structures.
(defconstant **L (hash-lens 'first 'last))
Signature
dict-lens :: Dict D => (&rest k) -> (D k a, (a -> b)) -> D k b
Like hash-lens
, but the resulting lenses work on dict structures instead.
(defconstant **L (dict-lens 'first 'last))
Signature
lget :: (a k y, ((a k y, (y -> z)))) -> z
Takes a structure and a lens and returns the value inside the structure.
(defconstant **L (hash-lens 'first))
(let ((u (hash :first "John" :last "Doe")))
(lget u **L.first)) ; => "John"
Signature
lset :: (a k y, ((a k y, (y -> z))), z) -> a k z
Takes a structure, a lens and a value and returns a new structure of the same type with the key set to the new value.
(defconstant **L (hash-lens 'first))
(let ((u (hash :first "John" :last "Doe")))
(lset u **L.first "Adam")) ; => (hash :first "Adam" :last "Doe")
Signature
lmap :: (a k y, ((a k y, (y -> z))), (y -> z)) -> a k z
Takes a structure, a lens and a function. Projects the function over the value in the structure and returns a new structure of the same type with the new value.
(defconstant **L (hash-lens 'first))
(let ((u (hash :first "John" :last "Doe")))
(lmap u ; => (hash :first "JOHN" :last "Doe")
**L.first
(#-> (.to-upper-case))))
Common algebraic types, ready to use.
Signature
coyo :: a -> coyo a (a -> b)
The coyo
structure allows to lift a non-functor value into the realm of functors. It implements the following methods:
c
= coyo instance
Method | Static? | Interface | Example |
---|---|---|---|
of |
Yes | Pointed | (coyo.of 1) |
lift |
Yes | (coyo.lift 1) |
|
to-string |
Show | (.to-string c) |
|
map |
Functor | (.map c (#-> ... )) |
|
lower |
(.lower c) |
||
reduce |
Foldable | (.reduce c (#(x v) ... ) x) |
(|> (coyo.lift "abcdef")
(.map (#-> (.split "")))
(.map (#-> (.reverse)))
(.map (#-> (.join "")))
(.reduce (#(a v) (+ a v)) ""))
Signature
free :: a -> free.result a | free.compute a (free a)
free
is a very useful monad that allows to build interpretable EDSL's from custom primitives into a program.
f, ff
= free instances
Method | Static? | Interface | Examples |
---|---|---|---|
of |
Yes | Pointed | (free.of 1) |
lift |
Yes | (free.lift T) |
|
map |
Functor | (.map f (#-> ... )) |
|
ap |
Applicative | (.ap f ff) |
|
flat-map / chain
|
Monad | (.flat-map f (#-> ... )) |
|
fold-map / interpret
|
Foldable | (.fold-map f (#-> ... ) T) |
;;; --- Primitive operations
(defsum http ((:GET url headers)
(:POST url data headers)))
(defun get (url)
(free.lift
(http.GET url (hash :"Accept" "application/json"))))
(defun post (url data)
(free.lift
(http.POST url data (hash :"Content-Type" "application/json"
:"Accept" "application/json"))))
;;; --- Interpreter function (=> natural transformation)
(defun interpret-task (x)
(match-sum x ((:GET (url headers)
(task (#(rej res)
(|>
(fetch url (hash :method "GET"
:&headers))
(.then map-http-status-to-state)
(.then res)
(.catch rej)))))
(:POST (url data headers)
(task (#(rej res)
(|>
(fetch url (hash :method "POST"
:&headers
:body (json-to-string data)))
(.then map-http-status-to-state)
(.then res)
(.catch rej))))))))
(defun map-http-status-to-state (response)
(with-fields respone (ok json status status-text)
(if ok
(json)
(future-reject (error status ":" status-text))))
;;; --- Program
(defun prog (user-id)
(|>
(get (+ "https://platform.tld/user/" user-id "/settings"))
(.flat-map (#(settings)
(post (+ "https://platform.tld/user/" user-id "/settings/save")
(hash-merge settings
(hash :key "value")))))
(.interpret interpret-task task)))
(|> (prog "jdoe")
(.run-task (#-> (console.warn))
(#> (console.log "Done!"))))
Signature
io :: (a -> b) -> io (a -> b)
io
encapsulates lazy function composition and application into a structure, useful for operations that have side-effects. It implements the following methods:
i, ii
= io instances
Method | Static? | Interface | Examples |
---|---|---|---|
of |
Yes | Pointed | (io.of 1) |
lift |
Yes | (io.lift 1) |
|
empty |
Yes | Monoid | (io.empty) |
identity |
Yes | Category | (io.identity) |
to-string |
Show | (.to-string i) |
|
equals |
Setoid | (.equals i ii) |
|
concat |
Semigroup | (.concat i ii) |
|
map |
Functor | (.map i (#-> ... )) |
|
contramap |
Contravariant | (.contramap i (#-> ... )) |
|
promap |
Profunctor | (.promap i (#-> ... ) (#-> ... )) |
|
ap |
Applicative | (.ap i ii) |
|
flat-map / chain
|
Monad | (.flat-map i (#-> ... )) |
|
compose |
Semigroupoid | (.compose i ii) |
|
run-io |
(.run-io i) |
(|> (io.lift (#-> (.query-selector "#my-div")))
(.map (#-> (.get-attribute "title")))
(.map (#-> (.to-upper-case)))
(.run-io document))
Signature
task :: ((Error -> _), (b -> c) -> _) -> task (Error -> _) (b -> c)
To model lazy asynchronous actions the task
structure is the way to go. Methods implemented by it are:
t, tt
= task instances
Method | Static? | Interface | Examples |
---|---|---|---|
of |
Yes | Pointed | (task.of 1) |
lift |
Yes | (task.lift 1) |
|
empty |
Yes | Monoid | (task.empty) |
zero |
Yes | Plus | (task.zero) |
resolve |
Yes | (task.resolve 1) |
|
reject |
Yes | (task.reject 1) |
|
to-string |
Show | (.to-string t) |
|
concat |
Semigroup | (.concat t tt) |
|
map |
Functor | (.map t (#-> ...)) |
|
bimap |
Bifunctor | (.bimap t (#-> ...) (#-> ...)) |
|
ap |
Applicative | (.ap t tt) |
|
flat-map / chain
|
Monad | (.flat-map t (#-> ...)) |
|
alt |
Alternative | (.alt t tt) |
|
run-task |
(.run-task t (#(e) ... ) (#(v) ...)) |
(|> (task.lift "https://api.my-site.com/users/")
(.flat-map (#(url)
(task (#(rej res)
(|> (fetch url)
(.then (#-> (.json) (res)))
(.catch rej))))))
(.map (#-> (.find (#-> (getf 'id) (eql? "u12345")))))
(.run-task (#(err)
(.warn console err))
(#(user)
(.log console user))))
Signature
maybe :: nil -> maybe.nothing | a -> maybe.just a
The maybe
sum type encapsulates the idea of null-checks (and - in JavaScript terms - undefined) and allows to avoid using conditionals. Useful whenever an operation might result in a null/undefined
return value. Implements:
m, mm
= maybe instances
Method | Static? | Interface | Examples |
---|---|---|---|
of |
Yes | Pointed | (maybe.of 1) |
lift |
Yes | (maybe.lift 1) |
|
empty |
Yes | Monoid | (maybe.empty) |
zero |
Yes | Plus | (maybe.zero) |
to-string |
Show | (.to-string m) |
|
equals |
Setoid | (.equals m mm) |
|
concat |
Semigroup | (.concat m mm) |
|
map |
Functor | (.map m (#-> ...)) |
|
bimap |
Bifunctor | (.bimap m (#-> ...) (#-> ...)) |
|
ap |
Applicative | (.ap m mm) |
|
flat-map / chain
|
Monad | (.flat-map m (#-> ...)) |
|
alt |
Alternative | (.alt m mm) |
|
reduce |
Foldable | (.reduce m (#(x v) ...) x) |
|
traverse |
Traversable | (.traverse m T.lift (#-> ...)) |
|
sequence |
Traversable | (.sequence m T.lift) |
(|> (maybe.lift "just a string")
(.map (#-> (.to-upper-case)))
(.map (#-> (.replace " " "-")))
(match-sum ((:nothing ()
(.log console "No value"))
(:just (value)
(.log console value)))))
Signature
maybe-transformer :: Monad T, Pointed T =>
T -> (nil -> maybe-t T maybe.nothing) |
T -> (a -> maybe-t T maybe.just a)
Lifts a pointed monad into a maybe-transformer
monad (maybe-t
). The transformer implements:
Method | Static? | Interface |
---|---|---|
of |
Yes | Pointed |
lift |
Yes | |
map |
Functor | |
flat-map / chain
|
Monad |
(defconstant dom-t (maybe-transformer io))
(|> (dom-t.lift document)
(.map (#-> (.query-selector "#my-selector")))
(.map (#-> (getf 'dataset 'foo)))
(getf 'stack)
(.run-io)
(match-sum ((:nothing ()
(.log console "No data"))
(:just (foo-data)
(.log console "Found:" foo-data)))))
Signature
either :: Error E =>
nil -> either.left E |
E -> either.left E |
a -> either.right a
Being a structure that is meant to make error handling more managable, either
is useful whenever an operation might result in an exception and you want to convey that information instead of throwing the error. Implements these interfaces:
e, ee
= either instances
Method | Static? | Interface | Examples |
---|---|---|---|
of |
Yes | Pointed | (either.of 1) |
lift |
Yes | (either.lift 1) |
|
empty |
Yes | Monoid | (either.empty) |
zero |
Yes | Plus | (either.zero) |
to-string |
Show | (.to-string e) |
|
equals |
Setoid | (.equals e ee) |
|
concat |
Semigroup | (.concat e ee) |
|
map |
Functor | (.map e (#-> ...)) |
|
bimap |
Bifunctor | (.bimap e (#-> ...) (#-> ...)) |
|
ap |
Applicative | (.ap e ee) |
|
flat-map / chain
|
Monad | (.flat-map e (#-> ...)) |
|
alt |
Alternative | (.alt e ee) |
|
reduce |
Foldable | (.reduce e (#(x v) ...) x) |
|
traverse |
Traversable | (.traverse e T.lift (#-> ...)) |
|
sequence |
Traversable | (.sequence e T.lift) |
(defun ensure-string (value)
(if (string? value)
(either.lift value)
(either.lift (error "The value " value " is not a string"))))
(|> (ensure-string "user input")
(.map (#-> (.to-upper-case)))
(.map (#-> (.replace " " "-")))
(match-sum ((:left (error)
(.log console (getf error 'message)))
(:right (value)
(.log console value)))))
Signature
either-transformer :: Monad T, Pointed T, Error E =>
T -> (nil -> either-t T either.left E) |
T -> (E -> either-t T either.left E) |
T -> (a -> either-t T either.right a)
Lifts a pointed monad into a either-transformer
monad (either-t
). The transformer implements:
Method | Static? | Interface |
---|---|---|
of |
Yes | Pointed |
lift |
Yes | |
map |
Functor | |
flat-map / chain
|
Monad |
Signature
seq :: a -> seq List a
A thin layer around native lists, seq
provides various methods that make lists traversable, applicatives, etc. The implemented interfaces:
s, ss
= seq instances
Method | Static? | Interface | Examples |
---|---|---|---|
of |
Yes | Pointed | (seq.of 1 2 3) |
lift |
Yes | (seq.lift 1) |
|
empty |
Yes | Monoid | (seq.empty) |
zero |
Yes | Plus | (seq.zero) |
to-string |
Show | (.to-string s) |
|
length |
(.length s) |
||
equals |
Setoid | (.equals s ss) |
|
concat |
Semigroup | (.concat s ss) |
|
map |
Functor | (.map s (#-> ...)) |
|
ap |
Applicative | (.ap s ss) |
|
flat-map / chain
|
Monad | (.flat-map s (#-> ...)) |
|
alt |
Alternative | (.alt s ss) |
|
reduce |
Foldable | (.reduce s (#(x v) ...) x) |
|
fold-map |
Foldable | (.fold-map s (#-> ...)) |
|
fold |
Foldable | (.fold s maybe.lift) |
|
traverse |
Traversable | (.traverse s maybe.lift (#-> ...)) |
|
sequence |
Traversable | (.sequence s maybe.lift) |
|
filter |
Filterable | (.filter s (#-> ...)) |
|
find |
(.find s (#-> ...)) |
||
cons |
(.cons s 1) |
||
snoc |
(.snoc s 1) |
||
take |
(.take s 3) |
||
take-while |
(.take-while s (#-> ...)) |
||
drop |
(.drop s 3) |
||
drop-while |
(.drop-while s (#-> ...)) |
||
reverse |
(.reverse s) |
(|> (seq.lift (list 1 2 3 4 5))
(.map (#-> (+ 1))
(.filter (#-> (even?)))
(.map (#(a)
(#(b)
(+ a ":" b))))
(.ap (seq.of "A" "B" "C"))
(.fold-map (#(x)
(+ x ", "))))) ; => A:2, B:4, C:6,
Signature
proof :: Error E, List L =>
nil -> proof.falsy L E |
E -> proof.falsy L E |
a -> proof.truthy a
Mostly similiar to either
, the proof
sum type is useful whenever operations might result in exceptions and you want to accumulate them. Usually, proof
is used for things like validation. Implements these interfaces:
Method | Static? | Interface | Examples |
---|---|---|---|
of |
Yes | Pointed | (proof.of 1) |
lift |
Yes | (proof.lift 1) |
|
empty |
Yes | Monoid | (proof.empty) |
zero |
Yes | Plus | (proof.zero) |
to-string |
Show | (.to-string p) |
|
equals |
Setoid | (.equals p pp) |
|
concat |
Semigroup | (.concat p pp) |
|
map |
Functor | (.map p (#-> ...)) |
|
bimap |
Bifunctor | (.bimap p (#-> ...)) |
|
ap |
Applicative | (.ap p pp) |
|
flat-map / chain
|
Monad | (.flat-map p (#-> ...)) |
|
alt |
Alternative | (.alt p pp) |
(defun is-not-empty (value)
(cond ((not (eql? "" (.trim value)))
(proof.lift value))
:else (proof.lift (error "EMPTY_VALUE"))))
(defun is-numeric (value)
(cond ((not (.test (regex "\\D") value))
(proof.lift value))
:else (proof.lift (error "NUMBERS_ONLY"))))
(defun validate (value)
(|> (is-not-empty value)
(.concat (is-numeric value))
(match-sum ((:falsy (errors)
(each exc errors
(.log console exc))
false)
(:truthy ()
(.log console "All right!")
true)))))
Signature
proof-transformer :: Monad T, Pointed T, Error E =>
T -> (nil -> proof-t T proof.falsy (list E)) |
T -> (E -> proof-t T proof.falsy (list E)) |
T -> (a -> proof-t T proof.truthy a)
Lifts a pointed monad into a proof-transformer
monad (proof-t
). The transformer implements:
Method | Static? | Interface |
---|---|---|
of |
Yes | Pointed |
lift |
Yes | |
map |
Functor | |
flat-map / chain
|
Monad |
Operations that transform an algebraic type into another algebraic type.
Signature
coyo-as-io :: coyo a (a -> b) -> io b
Signature
coyo-as-maybe :: coyo a (a -> b) -> maybe b
Signature
coyo-as-either :: coyo a (a -> b) -> either error b
Signature
coyo-as-proof :: coyo a (a -> b) -> proof (list error) b
Signature
coyo-as-task :: coyo a (a -> b) -> task (error -> _) b
Signature
maybe-as-either :: maybe a -> either error a
Signature
maybe-as-proof :: maybe a -> proof (list error) a
Signature
maybe-as-task :: maybe a -> task (error -> _) a
Signature
either-as-maybe :: either error a -> maybe a
Signature
either-as-proof :: either error a -> proof (list error) a
Signature
either-as-task :: either error a -> task (error -> _) a
Signature
proof-as-maybe :: proof (list error) a -> maybe a
Signature
proof-as-either :: proof (list error) a -> either error a
Signature
proof-as-task :: proof (list error) a -> task (error -> _) a
Signature
io-as-task :: io a -> task (error -> _) a