https://github.com/knutwalker/algebras
Reusable, composable, reasonably priced algebras for typeful effects and composable applications
https://github.com/knutwalker/algebras
Last synced: about 1 year ago
JSON representation
Reusable, composable, reasonably priced algebras for typeful effects and composable applications
- Host: GitHub
- URL: https://github.com/knutwalker/algebras
- Owner: knutwalker
- License: apache-2.0
- Created: 2015-04-02T02:27:01.000Z (about 11 years ago)
- Default Branch: master
- Last Pushed: 2015-04-02T16:24:45.000Z (about 11 years ago)
- Last Synced: 2025-06-05T11:19:59.378Z (about 1 year ago)
- Language: Scala
- Homepage:
- Size: 148 KB
- Stars: 6
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Algebras
Reusable, composable, reasonably priced algebras for typeful effects and composable applications.
## Install
From maven central
```
libraryDependencies += "de.knutwalker" %% "algebra-effect" % "0.1.0"
```
`Algebras` only depend on `scalaz-core`.
## Available modules
### Effect
`"de.knutwalker" %% "algebra-effect" % "0.1.0"`
`algebras.Effect` is a placeholder for the resulting effect type.
It is basically a wrapper around `scalaz.Free` and provides typical combinators like `map` and `flatMap`.
The `algebra-effect` module also contains syntax for arbitrary types that add an `effect[F[_]]` method to
some `A` to lift a value into an `Effect`.
### Log
`"de.knutwalker" %% "algebra-log" % "0.1.0"`
`algebras.Log` prodives an algebra for simple logging (`debug`, `info`, `warn`, `error`)
### Random
`"de.knutwalker" %% "algebra-random" % "0.1.0"`
`algebras.Random` prodives an very simple algebra for random number generation.
The basic method is `nextInt(n)` which returns an int in \[0, n) – i.e. n is the upper exclusive bound.
This is conform with `scala.util.Random.nextInt(Int)`
There are some additional methods, like `chooseInt(a, b)` which return an in in \[a, b] and `oneOf` which takes a variable number of `A`s and returns one of these `A`s.
## Using Effect
Any method, that produces an effect using an algebra must be paramterized in a type `F[_]`
with a context bound for the algebra that this method uses. The return type is `Effect[F, A]` where `A` is the methods actual return type.
At the end of the universe, you have a composed `Effect[F, A]` that you need to run.
To do so, first you have to combine all used algebras (their Ops, actually) into a super-algebra using `scalaz.Coproduct`
Then, you write interpreters for each algebra and combine those into an interpreter for the super-algebra.
An interpreter is a `scala.~>` for the algebra op and some monad.
All interpreters must use the same result monad.
Finally, run `effect.runM(interpreter)` to turn your `Effect[F, A]` into an `M[A]`.
Here's an example. Suppose we want to roll a dice...
```scala
import algebras._, Algebras._
import scalaz.{~>, Coproduct, Id}, Id.Id
object pureCore {
def roll[F[_]: Random]: Effect[F, Int] =
Random.chooseInt(1, 6)
def play[F[_]: Random : Log]: Effect[F, Int] = for {
_ ← Log.debug("rolling a dice...")
num ← roll
_ ← Log.debug(s"rolled a $num")
_ ← if (num == 6) Log.info("Yay! Rolled a 6!")
else ().effect[F]
} yield num
}
object edgeOfTheWorld {
type App[A] = Coproduct[RandomOp, LogOp, A]
val RandomInterpreter = new (RandomOp ~> Id) {
def apply[A](fa: RandomOp[A]): A = fa match {
case RandomOp.NextInt(n) ⇒ scala.util.Random.nextInt(n)
}
}
val LogInterpreter = new (LogOp ~> Id) {
def apply[A](fa: LogOp[A]): A = fa match {
case LogOp.Logs(msg, level, _) ⇒ println(s"[$level] $msg")
}
}
val AppInterpreter: App ~> Id = RandomInterpreter or LogInterpreter
val program: Effect[App, Int] = pureCore.play[App]
def main(args: Array[String]): Unit = {
program.runM(AppInterpreter)
}
}
```
## Interpreters
There are two basic interpreters already available.
`algebra-interpreter-rng` which uses [NICTA/rng](https://github.com/NICTA/rng) for the random number generation.
`algebra-interpreter-slf4j` which uses slf4j (duh) to log the messages.
Using these interpreters, the example above could be rewritten as
```scala
import algebras._, Algebras._
import scalaz.{~>, Coproduct, effect}, effect.IO
object pureCore {
def roll[F[_]: Random]: Effect[F, Int] =
Random.chooseInt(1, 6)
def play[F[_]: Random : Log]: Effect[F, Int] = for {
_ ← Log.debug("rolling a dice...")
num ← roll
_ ← Log.debug(s"rolled a $num")
_ ← if (num == 6) Log.info("Yay! Rolled a 6!")
else ().effect[F]
} yield num
}
object edgeOfTheWorld {
type App[A] = Coproduct[RandomOp, LogOp, A]
val AppInterpreter: App ~> IO = random.Interpreter or log.Interpreter
val program: Effect[App, Int] = pureCore.play[App]
def main(args: Array[String]): Unit = {
program.runM(AppInterpreter).unsafePerformIO()
}
}
```
## Acknowledgement
Based on https://www.parleys.com/tutorial/composable-application-architecture-reasonably-priced-monads
## License
Apache 2