The origami project provides "Monadic folds" to process streams of data in a composable fashion. Monadic folds come in 2 flavors:
min
, max
, average
, hash
,...The general form of a Fold
is
trait Fold[M[_], A, B] {
type S
def start: M[S]
def fold: (S, A) => M[S]
def end(s: S): M[B]
}
where:
M
must have a Monad
instanceA
is the type of input elements, being fed one by one to the foldB
is the final resultS
is the type of some internal statestart
is a method to "initialize" the foldend
is a method to "finalize" the foldfold
is the method called for each element A
and current type S
Folds can be composed to produce "larger" folds, doing several things at the same time. For example:
import org.atnos.origami._
import org.atnos.origami.fold._
import org.atnos.origami.folds._
import org.atnos.origami.syntax.foldable._
import cats.Eval
import cats.data.EitherT
import cats.implicits._
import java.io.PrintWriter
type Safe[A] = EitherT[Eval, Throwable, A]
def protect[A](a: =>A): Safe[A] = EitherT.right(Eval.later(a))
def saveToFile(path: String): Sink[Safe, Int] =
bracket(
// create a new writer
protect(new PrintWriter(path)))(
// write a new line in the file
(w, i: Int) => protect { w.write(s"i=$i\n"); w })(
// close the writer
w => protect(w.close))
val stats: Fold[Safe, Int, ((Int, Int), Double)] =
(minimumOr(0) <*> maximumOr(Int.MaxValue) <*> averageDouble).into[Safe] <*
saveToFile("target/readme-example")
val elements = (1 to 10).toList
elements.foldWith(stats).value.value
In the example above we create a stats
fold composed from:
<*>
(the zip
operator)saveToFile
using the Safe
monadThe Safe
monad is necessary here to use the bracket
combinator which creates a Fold
acquiring resources at the
beginning of the run and eventually release them. It needs both the ability to deal with errors (with EitherT
) and to
delay computations (with Eval
).