Scaling effects in Scala
Feb 10, 2019
42. A value. Different from 37 but both are numbers.
Effect is to type what type is to value.
π°s type allows to abstract over value, effect allows to abstract over type.
42: Int
"text": String
def opt[A](value: A): Option[A]
opt(42): Option[Int]
opt("text"): Option[String]
There are two types of effect scaling—horizontal β and vertical β .
Horizontal Scaling
The simplest scaling approach that replaces an effect with another one.
Built-in effects: Option[A] β Future[A]
.
Third-party effects: Future[A] β Task[A]
.
https://monix.io
import scala.concurrent.Future
import monix.eval.Task
import monix.execution.Scheduler.Implicits.global
val future = Future(42)
val task = Task.deferFuture(future) // Future[A] β Task[A]
task.runToFuture: Future[Int] // Task[A] β Future[A]
val io = task.toIO // Task[A] β IO[A]
io.to[Task]: Task[Int] // IO[A] β Task[A]
// Future[A] β Task[A] β IO[A] β Task[A] β Future[A]
Task.fromFuture(Future(42)).toIO.to[Task].runToFuture: Future[Int]
Horizontal scaling is bidirectional if types are isomorphic and unidirectional otherwise.
Vertical Scaling
βΌ With ability to abstract over types the next step is to abstract over effects.
Algebra is to effect what effect is to type.
One way to abstract over effects is the tagless final encoding.
import java.util.UUID
final case class Snapshot(tenantId: UUID, id: UUID)
trait SnapshotRepo[F[_]] {
def current(tenantId: UUID): F[Option[Snapshot]]
}
Another way is using Free monads. https://typelevel.org/cats/datatypes/freemonad.html
import cats.~>
import cats.free.Free
sealed trait ObjectStoreAlgebra[A]
final case class Put(path: String, bytes: Array[Byte]) extends ObjectStoreAlgebra[Unit]
object ObjectStore {
type ObjectStore[A] = Free[ObjectStoreAlgebra, A]
def put(path: String, bytes: Array[Byte]): ObjectStore[Unit] =
Free.liftF[ObjectStoreAlgebra, Unit](Put(path, bytes))
}
def compiler[F[_]]: ObjectStoreAlgebra ~> F = ???
β Algebras can be scaled horizontally by replacing one approach with another.
Extensible effects are to algebras what algebras are to effects. https://atnos-org.github.io/eff
import cats.Monad
import cats.data.Writer
import org.atnos.eff._
import org.atnos.eff.all._
sealed trait Event
final case class ObjectStored(path: String) extends Event
trait PayloadAlgebra[F[_]] {
type _effect[R] = F |= R // Expect to deal with F effect
type _writer[R] = Writer[String, ?] |= R // And the Cats' Writer for logging
def process[R: _option: _effect: _writer]( // Expect to see some Options
tenantId: UUID,
payload: Array[Byte],
snapshotRepo: SnapshotRepo[F], // Pass tagless final interpreter
compiler: ObjectStoreAlgebra ~> F // Pass free monad compiler
)(implicit M: Monad[F]): Eff[R, Event] = for {
_ β tell("Starting processing") // Cats data type
maybeSnapshot β Eff.send(snapshotRepo.current(tenantId)) // Tagless final algebra effect
snapshot β fromOption(maybeSnapshot) // Built-in Option type
path = s"${tenantId}/${snapshot.id}" // A value
_ β Eff.send(ObjectStore.put(path, payload).foldMap(compiler)) // Free monad effect
} yield ObjectStored(path)
}
β Vertical scaling implies both scaling up and scaling down.
β° Different effects along with algebra interpreters are computed in one place. https://typelevel.org/cats/datatypes/writer.html
β The API is scaled down being encoded as tagless final trait PayloadAlgebra[F[_]]
.
β Further downscaling is achieved by running the computation.
type Stack = Fx.fx3[Option, Task, Writer[String, ?]]
val algebra = new PayloadAlgebra[Task] {}
val computation: Task[Option[Event]] = algebra
.process[Stack](tenantId, payload, snapshotRepo, compiler)
.runOption
.runWriterUnsafe[String](println)
.runAsync
β§ Using the abstract interface, the computation results in nested effects.
β· The effect can be scaled horizontally as at the beginning.
flowchart BT subgraph Effects direction LR option["Option"] cats["Cats Writer"] future["Future"] task["Monix Task"] option -.- cats -.- future <-..-> task end subgraph Algebras direction LR tf[Tagless final] free[Free monad] tf <-..-> free end subgraph Stack[Effect Stack] eff[Extensible effects] end Effects <-..-> Algebras <-..-> Stack Effects <-..-> Stack
β»