Skip to content
Better reader monad for deeply-nested cakes
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
cats/src
examples-cats/src/main
examples-zio/src/main
kernel/src/main/scala/cakeless
project
zio/src
.gitignore
.scalafmt.conf
.travis.yml
LICENSE
README.md
build.sbt
sonatype.sbt

README.md

Project Current version Scala version Interop
Cakeless
0.4.0-SNAPSHOT
2.12.8
Cats, ZIO

codecov Build Status

Cats Friendly Badge

cakeless

Cakeless is library providing better reader monad for well-known cake pattern. It has 2 separate implementations (both depend on shapeless):

Both implementations share the same kernel and common functionality.

The difference is in naming convention (recover vs catchSome, etc.) and data type. For instance, cats implementation provides CakeT[F[_], A] (where F is arbitrary Monad) On the other hand, zio implementation provides CakeZ[Effect, Error, A] with more control over your effects and errors

Both implementations allows to wire your dependencies by hands and automatically (like macwire does)

To try it, add the following into your build.sbt file:

resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"

libraryDependencies ++= {
  val cakelessV = "0.4.0-SNAPSHOT"
  Seq(
    "ua.pp.itkpi" %% "cakeless-cats" % cakelessV
    // or "ua.pp.itkpi" %% "cakeless-zio" % cakelessV
  )
}

Assume you have such components:

import scala.concurrent.ExecutionContext
import java.nio.file.Path


trait ExecutionContextComponent {
  implicit def ec: ExecutionContext
}

trait FileConfigComponent {
  def configPath: Path
}

Using cakeless you can lift those components into Cake monad:

import cakaless._
import shapeless.{::, HNil}


val cake0: Cake.Aux[ExecutionContextComponent with FileConfigComponent, ExecutionContext :: Path :: HNil] = 
  cake[ExecutionContextComponent with FileConfigComponent]

As you can see cakeless automaticaly infers abstract defs and vals (e.g. dependencies that should be provided) and picks them up into type definition (as HList).

You can also lift another cake (it can't be too much cakes):

trait PropsComponent {
  def props: Map[String, String]
}

// component with self types
trait AllComponents2 { self: ExecutionContextComponent with PropsComponent =>
  def getPropAsync(prop: String): Future[Option[String]] = Future {
    props get prop
  }
}

...

val cake1: Cake.Aux[AllComponents2 with ExecutionContextComponent with PropsComponent, ExecutionContext :: Map[String, String] :: HNil] = 
  cake[AllComponents2 with ExecutionContextComponent with PropsComponent]

Than you can flatMap over cake:

val program: Cake.Aux[String, ExecutionContext :: Path :: Map[String, String] :: HNil] = 
  for {
    c0 <- cake0
    c1 <- cake1
  } yield "foo"

Cakeless will merge its dependencies using shapeless.ops.hlist.Union so that:

  1. Dependencies from different cakes will be accumulated along with computaitons
  2. Repeating dependencies (like 2 ExecutionContexts) will be merged into 1

You can then bake the cake:

val result: String = program.bake(ExecutionContext.global :: Paths.get("foo") :: Map("foo" -> "bar") :: HNil)

// or using case class so you can name dependencies explicitly
case class Wiring(ec: ExecutionContext, configPath: Path, props: Map[String, String])

// by hands
val result1: String = program bake Wiring(
  ec = ExecutionContext.global,
  configPath = Paths.get("foo"),
  props = Map("foo" -> "bar")
)

// or automatically when dependencies are defined in scope
val ec = ExecutionContext.global
val configPath = Paths.get("foo")
val props = Map("foo" -> "bar")

val result2: String = program.auto

For full example with cats.effect see here

For full example with zio see here

For automatic wiring samples see: cats auto zio auto

Lifecycle management integrations

See cats example

See zio example

You can’t perform that action at this time.