Skip to content

jessesherlock/Ergo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ergo

ergo [ur-goh, er-goh]; adverb; Consequently, therefore, hence.

Ergo is a small Clojure/Clojurescript library for iteration in a tranducer style, for iterating both regular functions and functions returning a core.async channel (for now, manifold coming soon).

Install

com.jessesherlock/ergo {:mvn/version "0.1.3-alpha"} / [com.jessesherlock/ergo "0.1.3-alpha"]

Iterate

Iterate takes a function f and a seed value x and returns an infinite list of x, (f x), (f (f x)), (f (f (f x))), etc The clojure core version returns a lazy list, but I find it very useful to use an eager version that can be used with other transducers, hence Ergo.

(require '[ergo.core :as ergo])
;; Exactly the clojure core lazy iterate if given two arguments
(ergo/iterate inc 1)
; (1 2 3 4 5 ....)

;; Used like a tranducer with 1 argument
(transduce (comp (ergo/iterate inc)
           (take 5))
           conj
           []
           [1])
; [1 2 3 4 5]

Using iterate as a transducer means it will take the first value in the collection being and use that as a seed, never touching any other values. Since iteration isn’t a type of reduction, it’s more of a sibling of the reducing concept (map is list->list, reduce is list->value, iterate is value->list), but it is still very useful to use it like this.

Ergo has a produce fn that works like transduce but takes a value instead of a collection so that you don’t have to wrap the seed in a collection all the time for no reason.

(ergo/produce (comp (ergo/iterate (partial * 2)) (take 5)) conj [] 1)
; [1 2 4 8 16]

Stopping iteration

Since (take n) to stop iteration isn’t viable for many/most use cases, there are a number of useful utility functions in ergo.core to stop on a nil value, when values reach a fixed point, until an arbitrary predicate returns true, until an exception is thrown and some others, as well as utility functions to build your own versions.

Look at the tests for examples of all of them.

Async

Like any transducer you can use iterate in an async transducing context (where the seed is in a core async channel)

(require '[clojure.core.async :as a])
(let [ch (a/chan)
      _ (a/onto-chan! ch [1])]
  (a/<!! (a/transduce (comp (ergo/iterate inc) (take 5)) conj [] ch)))
; [1 2 3 4 5]

But when using iterate for data pipelines/interceptors/state machines you will come across situations where your seed isn’t in a channel but your transition function, the one you want to iterate, returns a core.async channel.

This is what the ergo.async namespace is for.

(require '[clojure.core.async :as a])
(require '[ergo.async :as ea])

(defn async-inc [x] (a/go (inc x))) ; our example channel returning transition function

(a/<!! (transduce (comp (ea/iterate async-inc) (take 5)) conj [] [(a/to-chan! [1])]))
; [1 2 3 4 5]

ergo.async/iterate expects the seed to be in a channel (since it will be in a channel for every iteration but the first due to your async transition function)

It also has a useful version of produce

(a/<!! (ea/produce (comp (ea/iterate async-inc) (take 5)) conj [] 1))
; [1 2 3 4 5]

and a function called put-rf! which works like core.async/put! but can be used as a reducer, very useful for reducing/transducing values onto a core async channel.

When working with a pipeline of single values in an async context, especially if you process the collection of iteration steps then promise-channels are your friend, since normal mutable channels require a lot of care, ergo.async-utils has a number of functions for coercion to promise-channels as well as exception handling.

My main use case for this library is for state machines with transitions functions that may or may not return an async value, depending on the state, so there are also a number of utility functions for working with mixed values or coercing multiple types into a promise-channel.

Maintenance status

This library is being used for real world stuff, and is only marked alpha because I still have a bunch of projects that I will use it in, so it’s not impossible for API changes to happen. I consider it very unlikely, it is supported and will continue to be supported for the foreseeable future. Please file bugs, use it if you need it, and expect me to maintain this library. This is true as of 2024-09-01 and I will update that date semi regularly so you know this isn’t abandoned, the goal is to finish this library and make no more changes and these days people often read that as being abandoned, hence this message.

Walkthrough

TODO - the tests have good comments and should cover everything, and the library isn’t that large anyway but I should write up a walkthrough of everything at some point.

Contributing

Running the clj tests

clj -A:test -M -m koacha.runner

clj -A:test -M -m koacha.runner --watch

Running the cljs tests

clj -A:shadow watch test

Then open the listed webpage (the one after “HTTP server available at”) to see the test results

Fire up an nrepl server with rebel-readline, the tests, shadow and criterium in the classpath

clj -A:repl

About

Clojure library for transducer based Iteration

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published