- No previous CT knowledge or math foundations
- Leaving styles one is used to (ex. OOP)
- Lack of docs on how to properly use Monad Transformers and other techniques required to do concise FP.
- Rapid changing ecosystem
- Scala has not been designed to support first class typeclasses, sum types, etc.
- Proliferation of IO-like types
- scala.concurrent.Future
- fs2.Task
- monix.eval.Task
- cats.effects.IO
- Approachable to newcomers
- Stack-safe
- Dead simple integrations with Scala's library ecosystem
-
Freestyle programming style
-
**@free**, **@tagless**, **@modules**
-
Effects
-
Integrations
-
Optimizations (iota Coproduct, stack-safe @tagless)
-
What's next
@free trait Interact {
def ask(prompt: String): FS[String]
def tell(msg: String): FS[Unit]
}
implicit val handler: Interact.Handler[Future] = new Interact.Handler[Future] {
def ask(prompt: String): Future[String] = ???
def tell(msg: String): Future[Unit] = ???
}
+ @free trait Interact {
+ def ask(prompt: String): FS[String]
+ def tell(msg: String): FS[Unit]
+ }
- sealed trait Interact[A]
- case class Ask(prompt: String) extends Interact[String]
- case class Tell(msg: String) extends Interact[Unit]
-
- class Interacts[F[_]](implicit I: InjectK[Interact, F]) {
- def tell(msg: String): Free[F, Unit] = Free.inject[Interact, F](Tell(msg))
- def ask(prompt: String): Free[F, String] = Free.inject[Interact, F](Ask(prompt))
- }
-
- object Interacts {
- implicit def interacts[F[_]](implicit I: InjectK[Interact, F]): Interacts[F] = new Interacts[F]
- def apply[F[_]](implicit ev: Interacts[F]): Interacts[F] = ev
- }
+ @module trait App {
+ val exerciseOp: ExerciseOp
+ val userOp: UserOp
+ val userProgressOp: UserProgressOp
+ val githubOp: GithubOp
+ }
- type C01[A] = Coproduct[ExerciseOp, UserOp, A]
- type C02[A] = Coproduct[UserProgressOp, C01, A]
- type ExercisesApp[A] = Coproduct[GithubOp, C02, A]
- val exerciseAndUserInterpreter: C01 ~> M = exerciseOpsInterpreter or userOpsInterpreter
- val userAndUserProgressInterpreter: C02 ~> M = userProgressOpsInterpreter or exerciseAndUserInterpreter
- val allInterpreters: ExercisesApp ~> M = githubOpsInterpreter or userAndUserProgressInterpreter
- Declare your algebras
- Group them into modules
- Compose your programs
- Provide implicit implementations of each algebra Handler
- Run your programs at the edge of the world
Declare your algebras
import freestyle._
object algebras {
/* Handles user interaction */
@free trait Interact {
def ask(prompt: String): FS[String]
def tell(msg: String): FS[Unit]
}
/* Validates user input */
@tagless trait Validation {
def minSize(s: String, n: Int): FS[Boolean]
def hasNumber(s: String): FS[Boolean]
}
}
Combine your algebras in arbitrarily nested modules
import algebras._
import freestyle.effects.error._
import freestyle.effects.error.implicits._
import freestyle.effects.state
val st = state[List[String]]
import st.implicits._
object modules {
@module trait App {
val validation: Validation.StackSafe
val interact: Interact
val errorM : ErrorM
val persistence: st.StateM
}
}
Declare and compose programs
import cats.syntax.cartesian._
def program[F[_]]
(implicit I: Interact[F], R: st.StateM[F], E: ErrorM[F], V: Validation.StackSafe[F]): FreeS[F, Unit] = {
for {
cat <- I.ask("What's the kitty's name?")
isValid <- (V.minSize(cat, 5) |@| V.hasNumber(cat)).map(_ && _) //may run ops in parallel
_ <- if (isValid) R.modify(cat :: _) else E.error(new RuntimeException("invalid name!"))
cats <- R.get
_ <- I.tell(cats.toString)
} yield ()
}
Provide implicit evidence of your handlers to any desired target M[_]
import monix.eval.Task
import monix.cats._
import cats.syntax.flatMap._
import cats.data.StateT
type Target[A] = StateT[Task, List[String], A]
implicit val interactHandler: Interact.Handler[Target] = new Interact.Handler[Target] {
def ask(prompt: String): Target[String] = tell(prompt) >> StateT.lift(Task.now("Isidoro1"))
def tell(msg: String): Target[Unit] = StateT.lift(Task { println(msg) })
}
implicit val validationHandler: Validation.Handler[Target] = new Validation.Handler[Target] {
def minSize(s: String, n: Int): Target[Boolean] = StateT.lift(Task.now(s.length >= n))
def hasNumber(s: String): Target[Boolean] = StateT.lift(Task.now(s.exists(c => "0123456789".contains(c))))
}
Run your program to your desired target M[_]
import modules._
import freestyle.implicits._
import cats.instances.list._
import monix.execution.Scheduler.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._
val concreteProgram = program[App.Op]
val state = concreteProgram.interpret[Target]
val task = state.runEmpty
val asyncResult = task.runAsync
// What's the kitty's name?
// asyncResult: monix.execution.CancelableFuture[(List[String], Unit)] = monix.execution.CancelableFuture$Implementation@158ce21d
Await.result(asyncResult, 3.seconds)
// List(Isidoro1)
// res0: (List[String], Unit) = (List(Isidoro1),())
Error
import freestyle.effects.error._
import freestyle.effects.error.implicits._
import cats.instances.either._
type EitherTarget[A] = Either[Throwable, A]
def shortCircuit[F[_]: ErrorM] =
for {
a <- FreeS.pure(1)
b <- ErrorM[F].error[Int](new RuntimeException("BOOM"))
c <- FreeS.pure(1)
} yield a + b + c
shortCircuit[ErrorM.Op].interpret[EitherTarget]
// res1: EitherTarget[Int] = Left(java.lang.RuntimeException: BOOM)
shortCircuit[ErrorM.Op].interpret[Task]
// res2: monix.eval.Task[Int] = Task.FlatMap(Task.Suspend(monix.eval.Task$$$Lambda$9224/1847650055@614aa803), monix.eval.Task$$$Lambda$9225/164912834@76271a7)
Option
import freestyle.effects.option._
import freestyle.effects.option.implicits._
import cats.instances.option._
import cats.instances.list._
def programNone[F[_]: OptionM] =
for {
a <- FreeS.pure(1)
b <- OptionM[F].option[Int](None)
c <- FreeS.pure(1)
} yield a + b + c
programNone[OptionM.Op].interpret[Option]
// res3: Option[Int] = None
programNone[OptionM.Op].interpret[List]
// res4: List[Int] = List()
Validation
import freestyle.effects.validation
import cats.data.State
sealed trait ValidationError
case class NotValid(explanation: String) extends ValidationError
val v = validation[ValidationError]
import v.implicits._
type ValidationResult[A] = State[List[ValidationError], A]
def programErrors[F[_]: v.ValidationM] =
for {
_ <- v.ValidationM[F].invalid(NotValid("oh no"))
errs <- v.ValidationM[F].errors
_ <- v.ValidationM[F].invalid(NotValid("this won't be in errs"))
} yield errs
programErrors[v.ValidationM.Op].interpret[ValidationResult].runEmpty.value
// res5: (List[ValidationError], List[ValidationError]) = (List(NotValid(oh no), NotValid(this won't be in errs)),List(NotValid(oh no)))
An alternative to monad transformers
-
**error**: Signal errors
-
**either**: Flattens if `Right` / short-circuit `Left`
-
**option**: Flatten `Some` / short-circuit on `None`
-
**reader**: Deffer dependency injection until program interpretation
-
**writer**: Log / Accumulate values
-
**state**: Pure functional state threaded over the program monadic sequence
-
**traverse**: Generators over `Foldable`
-
**validation**: Accumulate and inspect errors throughout the monadic sequence
-
**async**: Integrate with callback based API's
-
**Monix**: Target runtime and `async` effect integration.
-
**Fetch**: Algebra to run fetch instances + Auto syntax `Fetch -> FS`.
-
**FS2**: Embed FS2 `Stream` in Freestyle programs.
-
**Doobie**: Embed `ConnectionIO` programs into Freestyle.
-
**Slick**: Embed `DBIO` programs into Freestyle.
-
**Akka Http**: `EntityMarshaller`s to return Freestyle programs in Akka-Http endpoints.
-
**Play**: Implicit conversions to return Freestyle programs in Play Actions.
-
**Twitter Util**: `Capture` instances for Twitter's `Future` & `Try`.
-
**Finch**: Mapper instances to return Freestyle programs in Finch endpoints.
-
**Http4s**: `EntityEncoder` instance to return Freestyle programs in Http4S endpoints.
- Create an algebra
@free sealed trait DoobieM {
def transact[A](f: ConnectionIO[A]): FS[A]
}
- Implement a handler declaring the target
M[_]
and whatever restrictions it may have
implicit def freeStyleDoobieHandler[M[_]: Catchable: Suspendable]
(implicit xa: Transactor[M]): DoobieM.Handler[M] =
new DoobieM.Handler[M] {
def transact[A](fa: ConnectionIO[A]): M[A] = fa.transact(xa)
}
- Optionally provide syntax for easy embedding into program's flow
implicit def freeSLiftDoobie[F[_]: DoobieM]: FreeSLift[F, ConnectionIO] =
new FreeSLift[F, ConnectionIO] {
def liftFSPar[A](cio: ConnectionIO[A]): FreeS.Par[F, A] = DoobieM[F].transact(cio)
}
- Use third party types interleaved with other algebras and effects
def loadUser[F[_]]
(userId: UserId)
(implicit
doobie: DoobieM[F],
logging: LoggingM[F]): FreeS[F, User] = {
import doobie.implicits._
for {
user <- sql"SELECT * FROM User WHERE userId = $userId"
.query[User]
.unique
.liftFS[F]
- <- logging.debug(s"Loaded User: ${user.userId}")
} yield user
}
Freestyle provides optimizations for Free + Inject + Coproduct compositions as in DataTypes a la Carte
A fast Coproduct type based on Iota with constant evaluation time based on
@scala.annotation.switch
on the Coproduct's internal indexed values.
import iota._
import iota.debug.options.ShowTrees
val interpreter: FSHandler[App.Op, Target] = CopK.FunctionK.summon
// <console>:90: generated tree:
// {
// final class $anon extends _root_.iota.CopKFunctionK[Op, Target] {
// private[this] val arr0 = scala.Predef.implicitly[cats.arrow.FunctionK[st.StateM.Op, Target]](st.implicits.freestyleStateMHandler[Target](cats.data.StateT.catsDataMonadStateForStateT[monix.eval.Task, List[String]](monix.cats.`package`.monixToCatsMonadRec[monix.eval.Task](monix.eval.Task.typeClassInstances)))).asInstanceOf[_root_.cats.arrow.FunctionK[Any, Target]];
// private[this] val arr1 = scala.Predef.implicitly[cats.arrow.FunctionK[freestyle.effects.error.ErrorM.Op, Target]](freestyle.effects.error.implicits.freeStyleErrorMHandler[Target](cats.data.StateT.catsDataMonadErrorForStateT[monix.eval.Task, List[String], Throwable](monix.cats.`package`.monixToCatsMonadError[monix.eval.Tas...interpreter: freestyle.FSHandler[modules.App.Op,Target] = CopKFunctionK[Op, Target]<<generated>>
Freestyle does not suffer from degrading performance as the number of Algebras increases in contrast
with cats.data.EitherK
(Work in progress)
Optimizations over the pattern matching of FunctionK
for user defined algebras to translate them
into a JVM switch with @scala.annotation.switch
.
(Work in progress)
Brings ADT-less stack safety to @tagless
Algebras
without rewriting interpreters to Free[M, ?]
where M[_]
is stack unsafe.
program[Option] // Stack-unsafe
program[StackSafe[Option]#F] // lift handlers automatically to Free[Option, ?] without the `@free` ADTs overhead
- More integrations
- More syntax and runtime optimizations
- IntelliJ IDEA support (scala meta)
- Akka actors integration
- Kafka client library
- Cassandra client library
- Microservice / RPC modules (Derive typesafe client and endpoints based on Protocol definitions)
- <Cats>
- <Simulacrum>
[colin-passiv](https://github.com/colin-passiv)
Adrián Ramírez Fornell <[AdrianRaFo](https://github.com/AdrianRaFo)>
Alejandro Gómez <[dialelo](https://github.com/dialelo)>
Ana Mª Marquez <[anamariamv](https://github.com/anamariamv)>
Andy Scott <[andyscott](https://github.com/andyscott)>
Diego Esteban Alonso Blas <[diesalbla](https://github.com/diesalbla)>
Domingo Valera <[dominv](https://github.com/dominv)>
Fede Fernández <[fedefernandez](https://github.com/fedefernandez)>
Francisco Diaz <[franciscodr](https://github.com/franciscodr)>
Giovanni Ruggiero <[gruggiero](https://github.com/gruggiero)>
Javi Pacheco <[javipacheco](https://github.com/javipacheco)>
Javier de Silóniz Sandino <[jdesiloniz](https://github.com/jdesiloniz)>
Jisoo Park <[guersam](https://github.com/guersam)>
Jorge Galindo <[jorgegalindocruces](https://github.com/jorgegalindocruces)>
Juan Pedro Moreno <[juanpedromoreno](https://github.com/juanpedromoreno)>
Juan Ramón González <[jrgonzalezg](https://github.com/jrgonzalezg)>
Maureen Elsberry <[MaureenElsberry](https://github.com/MaureenElsberry)>
Peter Neyens <[peterneyens](https://github.com/peterneyens)>
Raúl Raja Martínez <[raulraja](https://github.com/raulraja)>
Sam Halliday <[fommil](https://github.com/fommil)>
Suhas Gaddam <[suhasgaddam](https://github.com/suhasgaddam)>
http://frees.io @raulraja @47deg