diff --git a/.gitignore b/.gitignore index 6da9171..8766f35 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ - target/ lib_managed/ src_managed/ project/boot/ .history .cache +.idea diff --git a/README.md b/README.md index f743001..64d98bf 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,20 @@ > All versions are published to bintray https://bintray.com/projectseptemberinc/ +
+### v0.6.5: + +- first scala 2.12 support +- cross-compiling 2.11.8 & 2.12 +- depends on cats 0.8.0 + +
+### v0.6.1/2: + +- added `Freekit`/`Freekito` helpers to reduce `.freek` boilerplate in basic cases +- added `transpile` to flatten Free programs combining Free programs +- added `:&&:` operator to combine group of interpreters +
### v0.6.0: @@ -77,14 +91,14 @@ Freek is just a few helpers & tricks to make it straightforward to manipulate Fr ``` # in build.sbt -scalaVersion := "2.11.8" +scalaVersion := "2.12.0" // (or 2.11.8) resolvers += Resolver.bintrayRepo("projectseptemberinc", "maven") +scalacOptions := Seq("-Ypartial-unification") //if running 2.12 + libraryDependencies ++= Seq( - "com.projectseptember" %% "freek" % "0.6.0" // with cats-0.7.0 -// "com.projectseptember" %% "freek" % "0.6.0_cats-0.4.1" // with cats-0.4.1 -// "com.projectseptember" %% "freek" % "0.6.0_cats-0.6.1" // with cats-0.6.1 + "com.projectseptember" %% "freek" % "0.6.5" , "org.spire-math" %% "kind-projector" % "0.7.1" , "com.milessabin" %% "si2712fix-plugin" % "1.2.0" ) @@ -318,7 +332,7 @@ To prepend one or more DSL to an existing combination of DSL into a new program, for { _ <- Log.debug(s"Searching for value id: $id").freek[PRG] name <- KVS.Get(id).freek[PRG] - e <- DB.findById(id).freek[PRG] + e <- DB.findById(id).expand[PRG] file <- File.Get(e.file).freek[PRG] _ <- Log.debug(s"Found file:$file").freek[PRG] } yield (file) @@ -329,7 +343,7 @@ Please note: - there is no `NilDSL` at the end because it's brought by `DBService.PRG` - `:|:` also appends a list of DSL at the end - +- the use of the `expand` function instead of `freek` for the findById operation, the `expand` function allows the use of a program defined with a smaller DSL in one with a bigger DSL
@@ -617,6 +631,17 @@ To make the difference between `:|:` and `:||:`, please remind the following: - `:||:` is like operator `++` for Scala `Seq`, it appends 2 (coproduct) sequences of DSL. +
+#### Combine group of interpreters with `:&&:` + +```scala +val fooInterpreters = barInterpreter :&: logInterpreter :|: repoInterpreter +val barInterpreters = fooInterpreter :&: logInterpreter :|: repoInterpreter + +val interpreters = fooInterpreters :&&: barInterpreters +``` + +- `:&&:` is like operator `++` for Scala `Seq`, it appends 2 sequences of interpreters.
#### Unstack results with `.peelRight` / `.peelRight2` / `.peelRight3` @@ -660,6 +685,57 @@ Instead of `Foo2(i).freek[PRG].onionT[O].peelRight2`, you can write `Foo2(i).fre Instead of `Foo2(i).freek[PRG].onionT[O].peelRight3`, you can write `Foo2(i).freek[PRG].onionT3[O]` +
+#### Bored adding `.free[PRG]` on each line? Use `Freekit` trick + +```scala +type PRG = Foo1 :|: Foo2 :|: Log :|: NilDSL +val PRG = DSL.Make[PRG] + +// remark that you pass the value PRG here +object M extends Freekit(PRG) { + val prg = for { + aOpt <- Foo1.Bar1(7) + _ <- Log.Info(s"aOpt:$aOpt") + a <- aOpt match { + case Some(a) => for { + a <- Foo2.Bar21(a) + _ <- Log.Info(s"a1:$a") + } yield (a) + case None => for { + a <- Foo2.Bar22 + _ <- Log.Info(s"a2:$a") + } yield (a) + } + } yield (a) +} +``` + +This works in basic cases & naturally as soon as you have embedded `for-comprehension`, scalac inference makes it less efficient. + +
+#### Bored adding `.free[PRG].onionT[O]` on each line? Use `Freekito` trick + +```scala +type PRG = Foo1 :|: Foo2 :|: Log :|: NilDSL +val PRG = DSL.Make[PRG] + +// remark that you pass the value PRG here +object MO extends Freekito(PRG) { + // you need to create this type O to reify the Onion + type O = Option :&: Bulb + + val prg = for { + a <- Foo1.Bar1(7) + _ <- Log.Info(s"a:$a") + a <- Foo2.Bar21(a) + } yield (a) +} +``` + +This works in basic cases & naturally as soon as you have embedded `for-comprehension`, scalac inference makes it less efficient. + +

## Reminding motivations diff --git a/build.sbt b/build.sbt index c6f7e85..b8681e1 100644 --- a/build.sbt +++ b/build.sbt @@ -1,32 +1,40 @@ lazy val commonSettings = Seq( organization := "com.projectseptember" - , version := "0.6.0" + , version := "0.6.7" , resolvers ++= Seq( Resolver.mavenLocal , Resolver.sonatypeRepo("releases") - , Resolver.sonatypeRepo("snapshots") - ) + , Resolver.sonatypeRepo("snapshots")) , scalaVersion := "2.11.8" + , crossScalaVersions := Seq("2.11.8", "2.12.0", "2.12.1") , bintrayOrganization := Some("projectseptemberinc") , licenses += ("Apache-2.0", url("http://www.apache.org/licenses/LICENSE-2.0")) - , addCompilerPlugin("com.milessabin" % "si2712fix-plugin" % "1.2.0" cross CrossVersion.full) - , addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.7.1") - , libraryDependencies ++= Seq( - "org.typelevel" %% "cats" % "0.7.0" - , "com.milessabin" % "si2712fix-library" % "1.2.0" cross CrossVersion.full - , "org.scalatest" % "scalatest_2.11" % "3.0.0" % "test" - , "org.typelevel" %% "discipline" % "0.4" % "test" - , "org.typelevel" %% "cats-laws" % "0.6.1" - ) - + , addCompilerPlugin("org.spire-math" % "kind-projector" % "0.9.3" cross CrossVersion.binary) ) -lazy val root = (project in file(".")). - settings(commonSettings: _*). - settings( +def scalacOptionsVersion(scalaVersion: String) = { + Seq( + "-feature" + , "-language:higherKinds" + ) ++ (CrossVersion.partialVersion(scalaVersion) match { + case Some((2, scalaMajor)) if scalaMajor == 12 => Seq("-Ypartial-unification") + case _ => Nil + }) +} + +lazy val root = (project in file(".")) + .settings(commonSettings: _*) + .settings( name := "freek", - scalacOptions ++= Seq( - "-feature" - , "-language:higherKinds" - ) + scalacOptions ++= scalacOptionsVersion(scalaVersion.value) + ) + .settings( + libraryDependencies ++= Seq( + "org.typelevel" %% "cats-free" % "0.9.0" + , "org.scalatest" %% "scalatest" % "3.0.0" % Test + ) ++ (CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, scalaMajor)) if scalaMajor == 11 => + compilerPlugin("com.milessabin" % "si2712fix-plugin" % "1.2.0" cross CrossVersion.full) :: Nil + case _ => Nil + }) ) diff --git a/project/build.properties b/project/build.properties index a6e117b..27e88aa 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.8 +sbt.version=0.13.13 diff --git a/src/main/scala/CopK.scala b/src/main/scala/CopK.scala index b1786b9..1950cfe 100644 --- a/src/main/scala/CopK.scala +++ b/src/main/scala/CopK.scala @@ -1,7 +1,6 @@ package freek import cats.~> -import cats.data.Xor /** Higher-Kinded Coproduct (exactly like shapeless Coproduct but higher-kinded) @@ -372,6 +371,128 @@ trait PrependHKLower { } +} + + +trait AppendHK[L[_] <: CopK[_], H[_]] { + type Out[_] <: CopK[_] + + def apply[A](ha: L[A]): Out[A] + def single[A](ha: H[A]): Out[A] + + def nat[R[_], A](out: Out[A], nat2: L ~> R, nat1: H ~> R): R[A] +} + +object AppendHK extends AppendHKLower { + + def apply[L[_] <: CopK[_], H[_]] + (implicit prep: AppendHK[L, H]): Aux[L, H, prep.Out] = prep + + type Aux[L[_] <: CopK[_], H[_], Out0[_] <: CopK[_]] = AppendHK[L, H] { type Out[t] = Out0[t] } + + implicit def in1[H1[_], H2[_]]: Aux[In1[H1, ?], H2, In2[H1, H2, ?]] = + new AppendHK[In1[H1, ?], H2] { + type Out[t] = In2[H1, H2, t] + + def apply[A](c: In1[H1, A]): Out[A] = In2l(c.head) + def single[A](ha: H2[A]): Out[A] = In2r(ha) + + def nat[R[_], A](out: In2[H1, H2, A], nat1: In1[H1, ?] ~> R, nat2: H2 ~> R): R[A] = out match { + case In2l(l) => nat1(In1(l)) + case In2r(r) => nat2(r) + } + } + + implicit def in2[H1[_], H2[_], H3[_]]: Aux[In2[H1, H2, ?], H3, In3[H1, H2, H3, ?]] = + new AppendHK[In2[H1, H2, ?], H3] { + type Out[t] = In3[H1, H2, H3, t] + + def apply[A](c: In2[H1, H2, A]): Out[A] = c match { + case In2l(left) => In3l(left) + case In2r(right) => In3m(right) + } + + def single[A](ha: H3[A]): Out[A] = In3r(ha) + + def nat[R[_], A](out: In3[H1, H2, H3, A], nat1: In2[H1, H2, ?] ~> R, nat2: H3 ~> R): R[A] = out match { + case In3l(l) => nat1(In2l(l)) + case In3m(m) => nat1(In2r(m)) + case In3r(r) => nat2(r) + } + } + + implicit def in3[H1[_], H2[_], H3[_], H4[_]]: Aux[In3[H1, H2, H3, ?], H4, AppendK[In3[H1, H2, H3, ?], In1[H4, ?], ?]] = + new AppendHK[In3[H1, H2, H3, ?], H4] { + type Out[t] = AppendK[In3[H1, H2, H3, ?], In1[H4, ?], t] + + def apply[A](c: In3[H1, H2, H3, A]): Out[A] = Aplk(c) + + def single[A](ha: H4[A]): Out[A] = Aprk(In1(ha)) + + def nat[R[_], A](out: AppendK[In3[H1, H2, H3, ?], In1[H4, ?], A], nat1: In3[H1, H2, H3, ?] ~> R, nat2: H4 ~> R): R[A] = out match { + case Aplk(m) => nat1(m) + case Aprk(In1(l)) => nat2(l) + } + } + + implicit def append1[H1[_], H2[_], L[_] <: CopK[_]]: Aux[AppendK[L, In1[H1, ?], ?], H2, AppendK[L, In2[H1, H2, ?], ?]] = + new AppendHK[AppendK[L, In1[H1, ?], ?], H2] { + type Out[t] = AppendK[L, In2[H1, H2, ?], t] + + def apply[A](c: AppendK[L, In1[H1, ?], A]): Out[A] = c match { + case Aplk(l) => Aplk(l) + case Aprk(In1(r)) => Aprk(In2l(r)) + } + + def single[A](ha: H2[A]): Out[A] = Aprk(In2r(ha)) + + def nat[RR[_], A](out: AppendK[L, In2[H1, H2, ?], A], nat1: AppendK[L, In1[H1, ?], ?] ~> RR, nat2: H2 ~> RR): RR[A] = out match { + case Aplk(r) => nat1(Aplk(r)) + case Aprk(In2l(h1)) => nat1(Aprk(In1(h1))) + case Aprk(In2r(h2)) => nat2(h2) + } + } + + implicit def append2[H1[_], H2[_], H3[_], L[_] <: CopK[_]]: Aux[AppendK[L, In2[H1, H2, ?], ?], H3, AppendK[L, In3[H1, H2, H3, ?], ?]] = + new AppendHK[AppendK[L, In2[H1, H2, ?], ?], H3] { + type Out[t] = AppendK[L, In3[H1, H2, H3, ?], t] + + def apply[A](c: AppendK[L, In2[H1, H2, ?], A]): Out[A] = c match { + case Aplk(r) => Aplk(r) + case Aprk(In2l(h1)) => Aprk(In3l(h1)) + case Aprk(In2r(h2)) => Aprk(In3m(h2)) + } + + def single[A](ha: H3[A]): Out[A] = Aprk(In3r(ha)) + + def nat[RR[_], A](out: AppendK[L, In3[H1, H2, H3, ?], A], nat1: AppendK[L, In2[H1, H2, ?], ?] ~> RR, nat2: H3 ~> RR): RR[A] = out match { + case Aplk(l) => nat1(Aplk(l)) + case Aprk(In3l(h1)) => nat1(Aprk(In2l(h1))) + case Aprk(In3m(h2)) => nat1(Aprk(In2r(h2))) + case Aprk(In3r(h3)) => nat2(h3) + } + } + +} + + +trait AppendHKLower { + + implicit def append[H[_], L[_] <: CopK[_], R[_] <: CopK[_]]: AppendHK.Aux[AppendK[L, R, ?], H, AppendK[AppendK[L, R, ?], In1[H, ?], ?]] = + new AppendHK[AppendK[L, R, ?], H] { + type Out[t] = AppendK[AppendK[L, R, ?], In1[H, ?], t] + + def apply[A](c: AppendK[L, R, A]): Out[A] = Aplk(c) + + def single[A](ha: H[A]): Out[A] = Aprk(In1(ha)) + + def nat[RR[_], A](out: AppendK[AppendK[L, R, ?], In1[H, ?], A], nat1: AppendK[L, R, ?] ~> RR, nat2: H ~> RR): RR[A] = out match { + case Aplk(l) => nat1(l) + case Aprk(In1(h)) => nat2(h) + } + } + + } trait Replace[C[_] <: CopK[_], F[_], G[_]] { @@ -466,18 +587,182 @@ trait ReplaceLower { } } -// trait Flattener[F[_] <: CopK[_]] { -// type Out[_] <: CopK[_] -// def flatten[TC[_[_], _], F[_], A](t: TC[F, A]): TC[Out, A] -// } +trait Flattener[F[_] <: CopK[_], TC[_[_], _]] { + type Out[_] <: CopK[_] + def flatten[A](t: TC[F, A]): TC[Out, A] +} -// object Flattener { +object Flattener extends FlattenerLower{ + import cats.free.Free + type Aux[F[_] <: CopK[_], TC[_[_], _], Out0[_] <: CopK[_]] = Flattener[F, TC] { type Out[t] = Out0[t] } -// type Aux[F[_] <: CopK[_], Out0[_] <: CopK[_]] = Flattener[F] { type Out[t] = Out0[t] } + implicit def in1[F[_] <: CopK[_]]: Flattener.Aux[In1[Free[F, ?], ?], Free, F] = + new Flattener[In1[Free[F, ?], ?], Free] { + type Out[t] = F[t] -// implicit def one[F[_] <: CopK[_]] -// } + def flatten[A](tca: Free[In1[Free[F, ?], ?], A]): Free[F, A] = + tca.foldMap(new (In1[Free[F, ?], ?] ~> Free[F, ?]) { + def apply[A](in: In1[Free[F, ?], A]): Free[F, A] = in match { + case In1(free) => free + } + }) + } + + implicit def in2Left[F[_] <: CopK[_], R[_], O[_] <: CopK[_]]( + implicit ap: AppendHK.Aux[F, R, O] + ): Flattener.Aux[In2[Free[F, ?], R, ?], Free, O] = + new Flattener[In2[Free[F, ?], R, ?], Free] { + type Out[t] = O[t] + + def flatten[A](tca: Free[In2[Free[F, ?], R, ?], A]): Free[O, A] = + tca.foldMap(new (In2[Free[F, ?], R, ?] ~> Free[O, ?]) { + def apply[A](in: In2[Free[F, ?], R, A]): Free[O, A] = in match { + case In2l(free) => free.compile(new (F ~> O) { + def apply[A](fa: F[A]): O[A] = ap(fa) + }) + + case In2r(r) => Free.liftF(ap.single(r)) + } + }) + } + + implicit def in3Left[F[_] <: CopK[_], M[_], R[_], O1[_] <: CopK[_], O2[_] <: CopK[_]]( + implicit ap1: AppendHK.Aux[F, M, O1] + , ap2: AppendHK.Aux[O1, R, O2] + ): Flattener.Aux[In3[Free[F, ?], M, R, ?], Free, O2] = + new Flattener[In3[Free[F, ?], M, R, ?], Free] { + type Out[t] = O2[t] + + def flatten[A](tca: Free[In3[Free[F, ?], M, R, ?], A]): Free[O2, A] = + tca.foldMap(new (In3[Free[F, ?], M, R, ?] ~> Free[O2, ?]) { + def apply[A](in: In3[Free[F, ?], M, R, A]): Free[O2, A] = in match { + case In3l(free) => free.compile(new (F ~> O2) { + def apply[A](fa: F[A]): O2[A] = ap2(ap1(fa)) + }) + + case In3m(m) => Free.liftF(ap2(ap1.single(m))) + + case In3r(r) => Free.liftF(ap2.single(r)) + } + }) + } + +} + +trait FlattenerLower extends FlattenerLower2 { + import cats.free.Free + + implicit def in2Right[F[_] <: CopK[_], L[_], O[_] <: CopK[_]]( + implicit pr: PrependHK.Aux[L, F, O] + ): Flattener.Aux[In2[L, Free[F, ?], ?], Free, O] = + new Flattener[In2[L, Free[F, ?], ?], Free] { + type Out[t] = O[t] + + def flatten[A](tca: Free[In2[L, Free[F, ?], ?], A]): Free[O, A] = + tca.foldMap(new (In2[L, Free[F, ?], ?] ~> Free[O, ?]) { + def apply[A](in: In2[L, Free[F, ?], A]): Free[O, A] = in match { + case In2l(l) => Free.liftF(pr.single(l)) + + case In2r(free) => free.compile(new (F ~> O) { + def apply[A](fa: F[A]): O[A] = pr(fa) + }) + } + }) + } + + implicit def in3Middle[F[_] <: CopK[_], L[_], R[_], O1[_] <: CopK[_], O2[_] <: CopK[_]]( + implicit pr: PrependHK.Aux[L, F, O1] + , ap: AppendHK.Aux[O1, R, O2] + ): Flattener.Aux[In3[L, Free[F, ?], R, ?], Free, O2] = + new Flattener[In3[L, Free[F, ?], R, ?], Free] { + type Out[t] = O2[t] + + def flatten[A](tca: Free[In3[L, Free[F, ?], R, ?], A]): Free[O2, A] = + tca.foldMap(new (In3[L, Free[F, ?], R, ?] ~> Free[O2, ?]) { + def apply[A](in: In3[L, Free[F, ?], R, A]): Free[O2, A] = in match { + case In3l(l) => Free.liftF(ap(pr.single(l))) + + case In3m(free) => free.compile(new (F ~> O2) { + def apply[A](fa: F[A]): O2[A] = ap(pr(fa)) + }) + + case In3r(r) => Free.liftF(ap.single(r)) + } + }) + } + + + implicit def apkLeft[F[_] <: CopK[_], L[_] <: CopK[_], R[_] <: CopK[_], O[_] <: CopK[_]]( + implicit flt: Flattener.Aux[L, Free, O] + ): Flattener.Aux[AppendK[L, R, ?], Free, AppendK[O, R, ?]] = new Flattener[AppendK[L, R, ?], Free] { + type Out[t] = AppendK[O, R, t] + + def flatten[A](tca: Free[AppendK[L, R, ?], A]): Free[AppendK[O, R, ?], A] = + + tca.foldMap(new (AppendK[L, R, ?] ~> Free[AppendK[O, R, ?], ?]) { + def apply[A](in: AppendK[L, R, A]): Free[AppendK[O, R, ?], A] = in match { + case Aplk(l) => + val free = Free.liftF(l) + flt.flatten(free).compile(new (O ~> AppendK[O, R, ?]) { + def apply[A](oa: O[A]): AppendK[O, R, A] = Aplk(oa) + }) + + case Aprk(r) => Free.liftF(Aprk(r)) + } + }) + } + +} + +trait FlattenerLower2 { + import cats.free.Free + + implicit def in3Right[F[_] <: CopK[_], L[_], M[_], O1[_] <: CopK[_], O2[_] <: CopK[_]]( + implicit pr1: PrependHK.Aux[M, F, O1] + , pr2: PrependHK.Aux[L, O1, O2] + ): Flattener.Aux[In3[L, M, Free[F, ?], ?], Free, O2] = + new Flattener[In3[L, M, Free[F, ?], ?], Free] { + type Out[t] = O2[t] + + def flatten[A](tca: Free[In3[L, M, Free[F, ?], ?], A]): Free[O2, A] = + tca.foldMap(new (In3[L, M, Free[F, ?], ?] ~> Free[O2, ?]) { + def apply[A](in: In3[L, M, Free[F, ?], A]): Free[O2, A] = in match { + case In3l(l) => Free.liftF(pr2.single(l)) + + case In3m(m) => Free.liftF(pr2(pr1.single(m))) + + case In3r(free) => free.compile(new (F ~> O2) { + def apply[A](fa: F[A]): O2[A] = pr2(pr1(fa)) + }) + + } + }) + } + + + implicit def ApkRight[F[_] <: CopK[_], L[_] <: CopK[_], R[_] <: CopK[_], O[_] <: CopK[_]]( + implicit flt: Flattener.Aux[R, Free, O] + ): Flattener.Aux[AppendK[L, R, ?], Free, AppendK[L, O, ?]] = new Flattener[AppendK[L, R, ?], Free] { + type Out[t] = AppendK[L, O, t] + + def flatten[A](tca: Free[AppendK[L, R, ?], A]): Free[AppendK[L, O, ?], A] = + + tca.foldMap(new (AppendK[L, R, ?] ~> Free[AppendK[L, O, ?], ?]) { + def apply[A](in: AppendK[L, R, A]): Free[AppendK[L, O, ?], A] = in match { + case Aplk(l) => + Free.liftF(Aplk(l)) + + case Aprk(r) => + val free = Free.liftF(r) + flt.flatten(free).compile(new (O ~> AppendK[L, O, ?]) { + def apply[A](oa: O[A]): AppendK[L, O, A] = Aprk(oa) + }) + + } + }) + } +} // object CopAppend extends CopAppendLower { diff --git a/src/main/scala/Freekit.scala b/src/main/scala/Freekit.scala new file mode 100644 index 0000000..d92cccd --- /dev/null +++ b/src/main/scala/Freekit.scala @@ -0,0 +1,58 @@ +package freek + +import scala.reflect.macros.{ blackbox, whitebox } +import scala.reflect.macros.Context +import scala.language.experimental.macros +import scala.annotation.StaticAnnotation +import scala.annotation.compileTimeOnly + +import cats.free.Free + +import scala.language.implicitConversions + + +class Freekit[DSL0 <: DSL, C0[_] <: CopK[_]](val PRG: DSL.Make[DSL0, C0]) { + type PRG = PRG.DSL + type Cop[t] = PRG.Cop[t] + + implicit def liftFA[F[_], A](fa: F[A])( + implicit sub0: SubCop[In1[F, ?], Cop] + ): Free[Cop, A] = { + Freek.expand[In1[F, ?], Cop, A](Freek(fa))(sub0) + } + +} + +class Freekito[DSL0 <: DSL, C0[_] <: CopK[_]](val PRG: DSL.Make[DSL0, C0]) { + + type PRG = PRG.DSL + type Cop[t] = PRG.Cop[t] + type O <: Onion + + implicit def liftFGHA[F[_], G[_], HA, A](fga: F[G[HA]])( + implicit + ga: HKK.Aux[G[HA], A] + , sub0: SubCop[In1[F, ?], PRG.Cop] + , lifter2: Lifter2.Aux[G[HA], O, A] + , pointer: Pointer[O] + , mapper: Mapper[O] + , binder: Binder[O] + , traverser: Traverser[O] + ): OnionT[Free, PRG.Cop, O, A] = + OnionT.liftTHK(Freek.expand[In1[F, ?], PRG.Cop, G[HA]](Freek(fga))(sub0)) + + implicit def liftFA[F[_], A](fa: F[A])( + implicit + sub0: SubCop[In1[F, ?], PRG.Cop] + , pointer: Pointer[O] + , mapper: Mapper[O] + , binder: Binder[O] + , traverser: Traverser[O] + ): OnionT[Free, PRG.Cop, O, A] = + toOnionT0( + Freek.expand[In1[F, ?], PRG.Cop, A](Freek(fa))(sub0) + ).onionT[O] + +} + + diff --git a/src/main/scala/HasHoist.scala b/src/main/scala/HasHoist.scala index b5be7e7..ae48a17 100644 --- a/src/main/scala/HasHoist.scala +++ b/src/main/scala/HasHoist.scala @@ -2,7 +2,7 @@ package freek import scala.language.higherKinds -import cats.data.{ OptionT, XorT, Xor } +import cats.data.{ OptionT, EitherT } import cats.Functor import cats.free._ @@ -13,10 +13,10 @@ trait HasHoist[M[_]] { def liftF[F[_] : Functor, A](f: F[A]): T[F, A] } -class XorHasHoist[A] extends HasHoist[λ[t => Xor[A, t]]] { - type T[F[_], B] = XorT[F, A, B] - def liftT[F[_], B](f: F[Xor[A, B]]): XorT[F, A, B] = XorT.apply(f) - def liftF[F[_] : Functor, B](f: F[B]): XorT[F, A, B] = XorT.right(f) +class EitherHasHoist[A] extends HasHoist[λ[t => Either[A, t]]] { + type T[F[_], B] = EitherT[F, A, B] + def liftT[F[_], B](f: F[Either[A, B]]): EitherT[F, A, B] = EitherT.apply(f) + def liftF[F[_] : Functor, B](f: F[B]): EitherT[F, A, B] = EitherT.right(f) } object HasHoist { @@ -30,8 +30,8 @@ object HasHoist { def liftF[F[_] : Functor, B](f: F[B]): OptionT[F, B] = OptionT.liftF(f) } - implicit def xorHasHoist[A]: HasHoist.Aux[λ[t => Xor[A, t]], λ[(f[_], b) => XorT[f, A, b]]] = - new XorHasHoist[A] + implicit def eitherHasHoist[A]: HasHoist.Aux[λ[t => Either[A, t]], λ[(f[_], b) => EitherT[f, A, b]]] = + new EitherHasHoist[A] } diff --git a/src/main/scala/Interpreter.scala b/src/main/scala/Interpreter.scala index 6fc9804..5d56e71 100644 --- a/src/main/scala/Interpreter.scala +++ b/src/main/scala/Interpreter.scala @@ -22,6 +22,15 @@ class Interpreter[C[_] <: CopK[_], R[_]]( // // TBD // ) + def :&&:[D[_] <: CopK[_]](f: Interpreter[D, R]): Interpreter[AppendK[C, D, ?], R] = new Interpreter( + new ~>[AppendK[C, D, ?], R] { + def apply[A](c: AppendK[C, D, A]): R[A] = c match { + case Aplk(l) => nat.nat(l) + case Aprk(r) => f.nat(r) + } + } + ) + def andThen[R2[_]](r2: R ~> R2): Interpreter[C, R2] = new Interpreter( nat andThen r2 ) diff --git a/src/main/scala/Onion.scala b/src/main/scala/Onion.scala index e486368..066efad 100644 --- a/src/main/scala/Onion.scala +++ b/src/main/scala/Onion.scala @@ -372,4 +372,58 @@ trait Lifter2Low { } +} + + +trait PartialLifter1[HA, S <: Onion] { + type GA + def partialLift(ha: HA): S#Layers[GA] +} + +object PartialLifter1 { + type Aux[HA, S <: Onion, GA0] = PartialLifter1[HA, S] { type GA = GA0 } + + def apply[HA, S <: Onion](implicit lifter: PartialLifter1[HA, S]) = lifter + + + implicit def fga[F[_], GA0, O <: Onion]( + implicit lifter: Lifter[F, O] + ): PartialLifter1.Aux[F[GA0], O, GA0] = new PartialLifter1[F[GA0], O] { + type GA = GA0 + def partialLift(fga: F[GA0]): O#Layers[GA0] = lifter.lift(fga) + } + + // implicit def fga[F[_]: Functor, G[_], A0, O <: Onion]( + // implicit lifter: Lifter[F, O] + // ): PartialLifter1.Aux[F[G[A0]], O, G[A0]] = new PartialLifter1[F[G[A0]], O] { + // type GA = G[A0] + // def partialLift(fga: F[G[A0]]): O#Layers[G[A0]] = lifter.lift(fga) + // } + +} + +trait PartialLifter2[HA, S <: Onion] { + type GA + def partialLift(ha: HA): S#Layers[GA] +} + +object PartialLifter2 { + type Aux[HA, S <: Onion, GA0] = PartialLifter2[HA, S] { type GA = GA0 } + + def apply[HA, S <: Onion](implicit lifter: PartialLifter2[HA, S]) = lifter + + implicit def fga[F[_], G[_], HA0, O <: Onion]( + implicit lifter: Lifter[λ[t => F[G[t]]], O] + ): PartialLifter2.Aux[F[G[HA0]], O, HA0] = new PartialLifter2[F[G[HA0]], O] { + type GA = HA0 + def partialLift(fga: F[G[HA0]]): O#Layers[HA0] = lifter.lift(fga) + } + + // implicit def fga[F[_]: Functor, G[_], A0, O <: Onion]( + // implicit lifter: Lifter[F, O] + // ): PartialLifter1.Aux[F[G[A0]], O, G[A0]] = new PartialLifter1[F[G[A0]], O] { + // type GA = G[A0] + // def partialLift(fga: F[G[A0]]): O#Layers[G[A0]] = lifter.lift(fga) + // } + } \ No newline at end of file diff --git a/src/main/scala/OnionT.scala b/src/main/scala/OnionT.scala index 4c12e6c..2e3887e 100644 --- a/src/main/scala/OnionT.scala +++ b/src/main/scala/OnionT.scala @@ -2,7 +2,6 @@ package freek import cats.free.Free import cats.{Applicative, Functor, FlatMap, Monad, Traverse, Eq} -import cats.data.Xor /** The OnionT transformer to manipulate monadic stack of results */ @@ -148,6 +147,26 @@ object OnionT extends OnionTInstances { , traverser: Traverser[S] ): OnionT[TC, F, S, A] = OnionT(tcMonad.map(fa){ fa => lifter2.lift2(fa) }) + + def liftTPartial1[TC[_[_], _], F[_], S <: Onion, GA, A](fa: TC[F, GA])( + implicit + tcMonad: Monad[TC[F, ?]] + , liftp: PartialLifter1[GA, S] + , mapper: Mapper[S] + , binder: Binder[S] + // , traverser: Traverser[S] + ): OnionT[TC, F, S, liftp.GA] = + OnionT(tcMonad.map(fa){ fa => liftp.partialLift(fa) }) + + def liftTPartial2[TC[_[_], _], F[_], S <: Onion, GA, A](fa: TC[F, GA])( + implicit + tcMonad: Monad[TC[F, ?]] + , liftp: PartialLifter2[GA, S] + , mapper: Mapper[S] + , binder: Binder[S] + // , traverser: Traverser[S] + ): OnionT[TC, F, S, liftp.GA] = + OnionT(tcMonad.map(fa){ fa => liftp.partialLift(fa) }) } trait OnionTInstances { @@ -168,7 +187,12 @@ trait OnionTInstances { override def map[A, B](fa: OnionT[TC, F, S, A])(f: A => B): OnionT[TC, F, S, B] = fa.map(f) - def tailRecM[A, B](a: A)(f: A => OnionT[TC, F, S, Either[A, B]]): OnionT[TC, F, S, B] = defaultTailRecM(a)(f) + // unsafe + def tailRecM[A, B](a: A)(f: A => OnionT[TC, F, S, Either[A, B]]): OnionT[TC, F, S, B] = + f(a).flatMap { + case Left(nextA) => tailRecM(nextA)(f) + case Right(b) => pure(b) + } } diff --git a/src/main/scala/package.scala b/src/main/scala/package.scala index 0e590c1..a2334e4 100644 --- a/src/main/scala/package.scala +++ b/src/main/scala/package.scala @@ -50,9 +50,27 @@ package object freek extends LowerImplicits with HK { def interpret[F2[_] <: CopK[_], G[_]: Monad](i: Interpreter[F2, G])( implicit sub:SubCop[F, F2] - ): G[A] = free.foldMapUnsafe(new (F ~> G) { + ): G[A] = free.foldMap(new (F ~> G) { def apply[A](fa: F[A]): G[A] = i.nat(sub(fa)) }) + + def flatten[O[_] <: CopK[_]](implicit flt: Flattener.Aux[F, Free, O]): Free[O, A] = flt.flatten(free) + + def transpile[F2[_] <: CopK[_], G[_] <: CopK[_], O[_] <: CopK[_]](i: Interpreter[F2, G])( + implicit + sub:SubCop[F, F2] + , flt: Flattener.Aux[G, Free, O] + ): Free[O, A] = flt.flatten(free.compile(new (F ~> G) { + def apply[A](fa: F[A]): G[A] = i.nat(sub(fa)) + })) + + def transpile[F2[_] <: CopK[_], G[_] <: CopK[_], O[_] <: CopK[_]](i: F2 ~> G)( + implicit + sub:SubCop[F, F2] + , flt: Flattener.Aux[G, Free, O] + ): Free[O, A] = flt.flatten(free.compile(new (F ~> G) { + def apply[A](fa: F[A]): G[A] = i(sub(fa)) + })) } implicit class FreeExtend[F[_], A](val free: Free[F, A]) extends AnyVal { @@ -118,6 +136,23 @@ package object freek extends LowerImplicits with HK { , traverser: Traverser[O] ): OnionT[TC, F, O, GA] = OnionT.liftP(tc) + + @inline def onionX1[O <: Onion]( + implicit + tcMonad: Monad[TC[F, ?]] + , liftp: PartialLifter1[GA, O] + , mapper: Mapper[O] + , binder: Binder[O] + ): OnionT[TC, F, O, liftp.GA] = OnionT.liftTPartial1(tc) + + @inline def onionX2[O <: Onion]( + implicit + tcMonad: Monad[TC[F, ?]] + , liftp: PartialLifter2[GA, O] + , mapper: Mapper[O] + , binder: Binder[O] + ): OnionT[TC, F, O, liftp.GA] = OnionT.liftTPartial2(tc) + } implicit class toOnionExpand[C[_]<: CopK[_], O <: Onion, A](val onion: OnionT[Free, C, O, A]) { @@ -183,7 +218,7 @@ package object freek extends LowerImplicits with HK { } package freek { - trait LowerImplicits { + trait LowerImplicits extends LowerImplicits2 { implicit class toOnionT0[TC[_[_], _], F[_], A](val tc: TC[F, A]) { @inline def onionT[O <: Onion]( @@ -198,7 +233,11 @@ package freek { } - implicit def toInterpreter[F[_], R[_]](nat: F ~> R): Interpreter[In1[F, ?], R] = Interpreter(nat) + implicit def toInterpreterCopK[F[_] <: CopK[_], R[_]](nat: F ~> R): Interpreter[F, R] = new Interpreter(nat) } + + trait LowerImplicits2 { + implicit def toInterpreter[F[_], R[_]](nat: F ~> R): Interpreter[In1[F, ?], R] = Interpreter(nat) + } } \ No newline at end of file diff --git a/src/test/scala/AppSpec.scala b/src/test/scala/AppSpec.scala index 488a568..ec08521 100644 --- a/src/test/scala/AppSpec.scala +++ b/src/test/scala/AppSpec.scala @@ -7,7 +7,7 @@ package freek import org.scalatest._ import cats.free.{Free, Trampoline} -import cats.data.Xor +// import cats.data.Either import cats.{~>, Id} import scala.concurrent._ @@ -18,6 +18,7 @@ import cats.Functor import cats.instances.future._ import cats.instances.option._ import cats.instances.list._ +import cats.instances.either._ import ExecutionContext.Implicits.global import freek._ @@ -55,7 +56,7 @@ object DB { case object NotFound extends DBError sealed trait DSL[A] - case class FindById(id: String) extends DSL[Xor[DBError, Entity]] + case class FindById(id: String) extends DSL[Either[DBError, Entity]] } @@ -117,14 +118,14 @@ object Http { case object NAck extends SendStatus sealed trait HttpInteract[A] - case object HttpReceive extends HttpInteract[Xor[RecvError, HttpReq]] + case object HttpReceive extends HttpInteract[Either[RecvError, HttpReq]] case class HttpRespond(data: HttpResp) extends HttpInteract[SendStatus] - case class Stop(error: Xor[RecvError, SendStatus]) extends HttpInteract[Xor[RecvError, SendStatus]] + case class Stop(error: Either[RecvError, SendStatus]) extends HttpInteract[Either[RecvError, SendStatus]] object HttpInteract { def receive() = HttpReceive def respond(data: HttpResp) = HttpRespond(data) - def stop(err: Xor[RecvError, SendStatus]) = Stop(err) + def stop(err: Either[RecvError, SendStatus]) = Stop(err) } sealed trait HttpHandle[A] @@ -160,7 +161,7 @@ class AppSpec extends FlatSpec with Matchers { val PRG = DSL.Make[PRG] /** the DSL.Make */ - def findById(id: String): Free[PRG.Cop, Xor[DBError, Entity]] = + def findById(id: String): Free[PRG.Cop, Either[DBError, Entity]] = for { _ <- Log.debug("Searching for entity id:"+id).freek[PRG] res <- FindById(id).freek[PRG] @@ -185,8 +186,8 @@ class AppSpec extends FlatSpec with Matchers { resp <- HttpHandle.result( dbRes match { - case Xor.Left(err) => HttpResp(status = InternalServerError) - case Xor.Right(e) => HttpResp(status = Ok, body = e.toString) + case Left(err) => HttpResp(status = InternalServerError) + case Right(e) => HttpResp(status = Ok, body = e.toString) } ).freek[PRG] } yield (resp) @@ -197,20 +198,20 @@ class AppSpec extends FlatSpec with Matchers { // server DSL.Make // this is the worst case: recursive call so need to help scalac a lot // but in classic cases, it should be much more straighforward - def serve() : Free[PRG.Cop, Xor[RecvError, SendStatus]] = + def serve() : Free[PRG.Cop, Either[RecvError, SendStatus]] = for { recv <- HttpInteract.receive().freek[PRG] _ <- Log.info("HttpReceived Request:"+recv).freek[PRG] res <- recv match { - case Xor.Left(err) => HttpInteract.stop(Xor.left(err)).freek[PRG] + case Left(err) => HttpInteract.stop(Left(err)).freek[PRG] - case Xor.Right(req) => + case Right(req) => for { resp <- handle(req) _ <- Log.info("Sending Response:"+resp).freek[PRG] ack <- HttpInteract.respond(resp).freek[PRG] res <- if(ack == Ack) serve() - else HttpInteract.stop(Xor.right(ack)).freek[PRG] + else HttpInteract.stop(Right(ack)).freek[PRG] } yield (res) } } yield (res) @@ -231,7 +232,7 @@ class AppSpec extends FlatSpec with Matchers { def apply[A](a: DB.DSL[A]) = a match { case DB.FindById(id) => println(s"DB Finding $id") - Xor.right(Map("id" -> id, "name" -> "toto")) + Right(Map("id" -> id, "name" -> "toto")) } } @@ -249,9 +250,9 @@ class AppSpec extends FlatSpec with Matchers { case Http.HttpReceive => if(i < 10000) { i+=1 - Xor.right(Http.GetReq("/foo")) + Right(Http.GetReq("/foo")) } else { - Xor.left(Http.ClientDisconnected) + Left(Http.ClientDisconnected) } case Http.HttpRespond(resp) => Http.Ack @@ -285,18 +286,18 @@ class AppSpec extends FlatSpec with Matchers { sealed trait Foo[A] final case class Bar(s: String) extends Foo[Option[Int]] - final case class Bar2(i: Int) extends Foo[Xor[String, Int]] + final case class Bar2(i: Int) extends Foo[Either[String, Int]] final case object Bar3 extends Foo[Unit] type PRG = Foo :|: Log.DSL :|: NilDSL val PRG = DSL.Make[PRG] val prg = for { - i <- Bar("5").freek[PRG].liftT[Option].liftF[Xor[String, ?]] - i <- Bar2(i).freek[PRG].liftF[Option].liftT[Xor[String, ?]] - _ <- Log.info("toto " + i).freek[PRG].liftF[Option].liftF[Xor[String, ?]] - _ <- Log.infoF("").expand[PRG].liftF[Option].liftF[Xor[String, ?]] - _ <- Bar3.freek[PRG].liftF[Option].liftF[Xor[String, ?]] + i <- Bar("5").freek[PRG].liftT[Option].liftF[Either[String, ?]] + i <- Bar2(i).freek[PRG].liftF[Option].liftT[Either[String, ?]] + _ <- Log.info("toto " + i).freek[PRG].liftF[Option].liftF[Either[String, ?]] + _ <- Log.infoF("").expand[PRG].liftF[Option].liftF[Either[String, ?]] + _ <- Bar3.freek[PRG].liftF[Option].liftF[Either[String, ?]] } yield (()) val logger2FutureSkip = new (Log.DSL ~> Future) { @@ -309,7 +310,7 @@ class AppSpec extends FlatSpec with Matchers { val foo2FutureSkip = new (Foo ~> Future) { def apply[A](a: Foo[A]) = a match { case Bar(s) => Future { Some(s.toInt) } // if you put None here, it stops prg before Log - case Bar2(i) => Future(Xor.right(i)) + case Bar2(i) => Future(Right(i)) case Bar3 => Future.successful(()) } } @@ -328,17 +329,17 @@ class AppSpec extends FlatSpec with Matchers { sealed trait Foo[A] final case class Foo1(s: String) extends Foo[List[Option[Int]]] - final case class Foo2(i: Int) extends Foo[Xor[String, Int]] + final case class Foo2(i: Int) extends Foo[Either[String, Int]] final case object Foo3 extends Foo[Unit] - final case class Foo4(i: Int) extends Foo[Xor[String, Option[Int]]] + final case class Foo4(i: Int) extends Foo[Either[String, Option[Int]]] sealed trait Bar[A] final case class Bar1(s: String) extends Bar[Option[String]] - final case class Bar2(i: Int) extends Bar[Xor[String, String]] + final case class Bar2(i: Int) extends Bar[Either[String, String]] type PRG2 = Bar :|: Log.DSL :|: NilDSL - type O = List :&: Xor[String, ?] :&: Option :&: Bulb + type O = List :&: Either[String, ?] :&: Option :&: Bulb type PRG = Foo :|: Log.DSL :|: PRG2 val PRG = DSL.Make[PRG] @@ -362,16 +363,16 @@ class AppSpec extends FlatSpec with Matchers { val foo2Future = new (Foo ~> Future) { def apply[A](a: Foo[A]) = a match { case Foo1(s) => Future { List(Some(s.toInt)) } // if you put None here, it stops prg before Log - case Foo2(i) => Future(Xor.right(i)) + case Foo2(i) => Future(Right(i)) case Foo3 => Future.successful(()) - case Foo4(i) => Future.successful(Xor.right(Some(i))) + case Foo4(i) => Future.successful(Right(Some(i))) } } val bar2Future = new (Bar ~> Future) { def apply[A](a: Bar[A]) = a match { case Bar1(s) => Future { Some(s) } // if you put None here, it stops prg before Log - case Bar2(i) => Future(Xor.right(i.toString)) + case Bar2(i) => Future(Right(i.toString)) } } @@ -389,17 +390,17 @@ class AppSpec extends FlatSpec with Matchers { sealed trait Foo[A] final case class Foo1(s: String) extends Foo[Option[Int]] - final case class Foo2(i: Int) extends Foo[Xor[String, Int]] + final case class Foo2(i: Int) extends Foo[Either[String, Int]] final case object Foo3 extends Foo[Unit] - final case class Foo4(i: Int) extends Foo[Xor[String, Option[Int]]] + final case class Foo4(i: Int) extends Foo[Either[String, Option[Int]]] sealed trait Bar[A] final case class Bar1(s: String) extends Bar[List[Option[String]]] - final case class Bar2(i: Int) extends Bar[Xor[String, String]] + final case class Bar2(i: Int) extends Bar[Either[String, String]] type PRG2 = Bar :|: Log.DSL :|: NilDSL - type O = List :&: Xor[String, ?] :&: Bulb + type O = List :&: Either[String, ?] :&: Bulb type PRG = Foo :|: Log.DSL :|: PRG2 val PRG = DSL.Make[PRG] @@ -426,16 +427,16 @@ class AppSpec extends FlatSpec with Matchers { val foo2Future = new (Foo ~> Future) { def apply[A](a: Foo[A]) = a match { case Foo1(s) => Future { Some(s.toInt) } // if you put None here, it stops prg before Log - case Foo2(i) => Future(Xor.right(i)) + case Foo2(i) => Future(Right(i)) case Foo3 => Future.successful(()) - case Foo4(i) => Future.successful(Xor.right(Some(i))) + case Foo4(i) => Future.successful(Right(Some(i))) } } val bar2Future = new (Bar ~> Future) { def apply[A](a: Bar[A]) = a match { case Bar1(s) => Future { List(Some(s)) } // if you put None here, it stops prg before Log - case Bar2(i) => Future(Xor.right(i.toString)) + case Bar2(i) => Future(Right(i.toString)) } } @@ -453,17 +454,17 @@ class AppSpec extends FlatSpec with Matchers { sealed trait Foo[A] final case class Foo1(s: String) extends Foo[Option[Int]] - final case class Foo2(i: Int) extends Foo[Xor[String, Int]] + final case class Foo2(i: Int) extends Foo[Either[String, Int]] final case object Foo3 extends Foo[Unit] - final case class Foo4(i: Int) extends Foo[Xor[String, Option[Int]]] + final case class Foo4(i: Int) extends Foo[Either[String, Option[Int]]] sealed trait Bar[A] final case class Bar1(s: String) extends Bar[List[Option[String]]] - final case class Bar2(i: Int) extends Bar[Xor[String, String]] + final case class Bar2(i: Int) extends Bar[Either[String, String]] type PRG2 = Bar :|: Log.DSL :|: NilDSL - type O = List :&: Xor[String, ?] :&: Option :&: Bulb + type O = List :&: Either[String, ?] :&: Option :&: Bulb type PRG = Foo :|: Log.DSL :|: PRG2 val PRG = DSL.Make[PRG] @@ -493,16 +494,16 @@ class AppSpec extends FlatSpec with Matchers { val foo2Future = new (Foo ~> Future) { def apply[A](a: Foo[A]) = a match { case Foo1(s) => Future { Some(s.toInt) } // if you put None here, it stops prg before Log - case Foo2(i) => Future(Xor.right(i)) + case Foo2(i) => Future(Right(i)) case Foo3 => Future.successful(()) - case Foo4(i) => Future.successful(Xor.right(Some(i))) + case Foo4(i) => Future.successful(Right(Some(i))) } } val bar2Future = new (Bar ~> Future) { def apply[A](a: Bar[A]) = a match { case Bar1(s) => Future { List(Some(s)) } // if you put None here, it stops prg before Log - case Bar2(i) => Future(Xor.right(i.toString)) + case Bar2(i) => Future(Right(i.toString)) } } @@ -525,17 +526,17 @@ class AppSpec extends FlatSpec with Matchers { sealed trait Foo[A] final case class Foo1(s: String) extends Foo[List[Option[Int]]] - final case class Foo2(i: Int) extends Foo[Xor[String, Int]] + final case class Foo2(i: Int) extends Foo[Either[String, Int]] final case object Foo3 extends Foo[Unit] - final case class Foo4(i: Int) extends Foo[Xor[String, Option[Int]]] + final case class Foo4(i: Int) extends Foo[Either[String, Option[Int]]] sealed trait Bar[A] final case class Bar1(s: String) extends Bar[Option[String]] - final case class Bar2(i: Int) extends Bar[Xor[String, String]] + final case class Bar2(i: Int) extends Bar[Either[String, String]] type PRG2 = Bar :|: Log.DSL :|: NilDSL - type O = List :&: Xor[String, ?] :&: Option :&: Bulb + type O = List :&: Either[String, ?] :&: Option :&: Bulb type PRG = Foo :|: Log.DSL :|: KVS[String, Int, ?] :|: PRG2 val PRG = DSL.Make[PRG] @@ -561,16 +562,16 @@ class AppSpec extends FlatSpec with Matchers { val foo2Future = new (Foo ~> Future) { def apply[A](a: Foo[A]) = a match { case Foo1(s) => Future { List(Some(s.toInt)) } // if you put None here, it stops prg before Log - case Foo2(i) => Future(Xor.right(i)) + case Foo2(i) => Future(Right(i)) case Foo3 => Future.successful(()) - case Foo4(i) => Future.successful(Xor.right(Some(i))) + case Foo4(i) => Future.successful(Right(Some(i))) } } val bar2Future = new (Bar ~> Future) { def apply[A](a: Bar[A]) = a match { case Bar1(s) => Future { Some(s) } // if you put None here, it stops prg before Log - case Bar2(i) => Future(Xor.right(i.toString)) + case Bar2(i) => Future(Right(i.toString)) } } @@ -585,8 +586,7 @@ class AppSpec extends FlatSpec with Matchers { val interpreters = foo2Future :&: logger2Future :&: bar2Future :&: kvs2Future - Await.result(prg.value.interpret(interpreters), 10.seconds) - + Await.result(prg.value.interpret(interpreters), 10.seconds) } "freek" should "manage monadic onions of result types wrap/peelRight" in { @@ -596,19 +596,19 @@ class AppSpec extends FlatSpec with Matchers { sealed trait Bar[A] final case class Bar1(s: String) extends Bar[List[Option[String]]] - final case class Bar2(i: Int) extends Bar[Xor[String, String]] + final case class Bar2(i: Int) extends Bar[Either[String, String]] type PRG2 = Bar :|: Log.DSL :|: NilDSL - type O = List :&: Xor[String, ?] :&: Option :&: Bulb + type O = List :&: Either[String, ?] :&: Option :&: Bulb type PRG = Foo :|: Log.DSL :|: PRG2 val PRG = DSL.Make[PRG] - val f: OnionT[Free, PRG.Cop, List :&: Xor[String, ?] :&: Bulb, Option[Int]] = + val f: OnionT[Free, PRG.Cop, List :&: Either[String, ?] :&: Bulb, Option[Int]] = Foo1("5") .freek[PRG] - .onionT[Xor[String, ?] :&: Option :&: Bulb] + .onionT[Either[String, ?] :&: Option :&: Bulb] .wrap[List] .peelRight @@ -622,17 +622,17 @@ class AppSpec extends FlatSpec with Matchers { sealed trait Foo[A] final case class Foo1(s: String) extends Foo[List[Option[Int]]] - final case class Foo2(i: Int) extends Foo[Xor[String, Int]] + final case class Foo2(i: Int) extends Foo[Either[String, Int]] final case object Foo3 extends Foo[Unit] - final case class Foo4(i: Int) extends Foo[Xor[String, Option[Int]]] + final case class Foo4(i: Int) extends Foo[Either[String, Option[Int]]] sealed trait Bar[A] final case class Bar1(s: String) extends Bar[Option[String]] - final case class Bar2(i: Int) extends Bar[Xor[String, String]] + final case class Bar2(i: Int) extends Bar[Either[String, String]] type PRG2 = Bar :|: Log.DSL :|: NilDSL - type O = List :&: Xor[String, ?] :&: Option :&: Bulb + type O = List :&: Either[String, ?] :&: Option :&: Bulb type PRG = Foo :|: Log.DSL :|: PRG2 val PRG = DSL.Make[PRG] @@ -656,16 +656,16 @@ class AppSpec extends FlatSpec with Matchers { val foo2Future = new (Foo ~> Future) { def apply[A](a: Foo[A]) = a match { case Foo1(s) => Future { List(Some(s.toInt)) } // if you put None here, it stops prg before Log - case Foo2(i) => Future(Xor.right(i)) + case Foo2(i) => Future(Right(i)) case Foo3 => Future.successful(()) - case Foo4(i) => Future.successful(Xor.right(Some(i))) + case Foo4(i) => Future.successful(Right(Some(i))) } } val bar2Future = new (Bar ~> Future) { def apply[A](a: Bar[A]) = a match { case Bar1(s) => Future { Some(s) } // if you put None here, it stops prg before Log - case Bar2(i) => Future(Xor.right(i.toString)) + case Bar2(i) => Future(Right(i.toString)) } } @@ -683,13 +683,13 @@ class AppSpec extends FlatSpec with Matchers { sealed trait RepoF[A] sealed trait Repo[A] - case class Query(no: String) extends Repo[Xor[String, Account]] - case class Store(account: Account) extends Repo[Xor[String, Account]] - case class Delete(no: String) extends Repo[Xor[String, Unit]] + case class Query(no: String) extends Repo[Either[String, Account]] + case class Store(account: Account) extends Repo[Either[String, Account]] + case class Delete(no: String) extends Repo[Either[String, Unit]] object Repo { type PRG = Repo :|: NilDSL - type O = Xor[String, ?] :&: Bulb + type O = Either[String, ?] :&: Bulb } def query(no: String) = Query(no) @@ -706,9 +706,9 @@ class AppSpec extends FlatSpec with Matchers { trait FooLayer extends RepositoryLayer { sealed trait Foo[A] final case class Foo1(s: String) extends Foo[List[Option[Int]]] - final case class Foo2(i: Int) extends Foo[Xor[String, Int]] + final case class Foo2(i: Int) extends Foo[Either[String, Int]] final case object Foo3 extends Foo[Unit] - final case class Foo4(i: Int) extends Foo[Xor[String, Option[Int]]] + final case class Foo4(i: Int) extends Foo[Either[String, Option[Int]]] object Foo { type PRG = Foo :|: Log.DSL :|: Repo.PRG @@ -719,7 +719,7 @@ class AppSpec extends FlatSpec with Matchers { sealed trait Bar[A] final case class Bar1(s: String) extends Bar[Option[String]] - final case class Bar2(i: Int) extends Bar[Xor[String, String]] + final case class Bar2(i: Int) extends Bar[Either[String, String]] object Bar { type PRG = Bar :|: Log.DSL :|: Repo.PRG @@ -731,7 +731,7 @@ class AppSpec extends FlatSpec with Matchers { extends FooLayer with BarLayer { - type O = List :&: Xor[String, ?] :&: Option :&: Bulb + type O = List :&: Either[String, ?] :&: Option :&: Bulb type PRG = Log.DSL :|: Bar.PRG :||: Foo.PRG val PRG = DSL.Make[PRG] @@ -756,31 +756,37 @@ class AppSpec extends FlatSpec with Matchers { val foo2Future = new (Foo ~> Future) { def apply[A](a: Foo[A]) = a match { case Foo1(s) => Future { println(s); List(Some(s.toInt)) } // if you put None here, it stops prg before Log - case Foo2(i) => Future(Xor.right(i)) + case Foo2(i) => Future(Right(i)) case Foo3 => Future.successful(()) - case Foo4(i) => Future.successful(Xor.right(Some(i))) + case Foo4(i) => Future.successful(Right(Some(i))) } } val bar2Future = new (Bar ~> Future) { def apply[A](a: Bar[A]) = a match { case Bar1(s) => Future { Some(s) } // if you put None here, it stops prg before Log - case Bar2(i) => Future(Xor.right(i.toString)) + case Bar2(i) => Future(Right(i.toString)) } } val repo2Future = new (Repo ~> Future) { def apply[A](a: Repo[A]) = a match { - case Query(s) => Future { Xor.right(new Account {}) } - case Store(acc) => Future { Xor.right(new Account {}) } - case Delete(no) => Future { Xor.right(()) } + case Query(s) => Future { Right(new Account {}) } + case Store(acc) => Future { Right(new Account {}) } + case Delete(no) => Future { Right(()) } } } + val fooInterpreters = foo2Future :&: logger2Future :&: repo2Future + val barInterpreters = bar2Future :&: logger2Future :&: repo2Future + val interpreters = foo2Future :&: logger2Future :&: bar2Future :&: repo2Future + val interpreters2 = logger2Future :&: fooInterpreters :&&: barInterpreters } val r = Await.result(Prg.prg.value.interpret(Prg.interpreters), 10.seconds) println("result:"+r) + val r2 = Await.result(Prg.prg.value.interpret(Prg.interpreters2), 10.seconds) + println("result:"+r2) } @@ -821,15 +827,15 @@ class AppSpec extends FlatSpec with Matchers { final case class Bar22(s: Int) extends Foo2[List[Option[Int]]] sealed trait Foo3[A] - final case class Bar31(s: Long) extends Foo3[Xor[String, Long]] - final case class Bar32(s: Float) extends Foo3[Xor[String, List[Float]]] - final case class Bar33(s: Double) extends Foo3[Xor[String, Option[Boolean]]] + final case class Bar31(s: Long) extends Foo3[Either[String, Long]] + final case class Bar32(s: Float) extends Foo3[Either[String, List[Float]]] + final case class Bar33(s: Double) extends Foo3[Either[String, Option[Boolean]]] type PRG = Foo1 :|: Foo2 :|: Foo3 :|: NilDSL val PRG = DSL.Make[PRG] - type O = Xor[String, ?] :&: List :&: Option :&: Bulb + type O = Either[String, ?] :&: List :&: Option :&: Bulb - val f1: Free[PRG.Cop, Xor[String, List[Option[Unit]]]] = (for { + val f1: Free[PRG.Cop, Either[String, List[Option[Unit]]]] = (for { i <- Bar1(3).freek[PRG].onionT[O] i <- Bar21(i).freek[PRG].onionT[O] i <- Bar22(i).freek[PRG].onionT[O] @@ -849,17 +855,17 @@ class AppSpec extends FlatSpec with Matchers { final case class Bar22(s: Int) extends Foo2[List[Option[Int]]] sealed trait Foo3[A] - final case class Bar31(s: Int) extends Foo3[Xor[String, Long]] - final case class Bar32(s: Float) extends Foo3[Xor[String, List[Float]]] - final case class Bar33(s: Double) extends Foo3[Xor[String, Option[Boolean]]] - final case class Bar34(s: Double) extends Foo3[Xor[String, List[Option[Boolean]]]] + final case class Bar31(s: Int) extends Foo3[Either[String, Long]] + final case class Bar32(s: Float) extends Foo3[Either[String, List[Float]]] + final case class Bar33(s: Double) extends Foo3[Either[String, Option[Boolean]]] + final case class Bar34(s: Double) extends Foo3[Either[String, List[Option[Boolean]]]] type PRG = Foo1 :|: Foo2 :|: Foo3 :|: NilDSL val PRG = DSL.Make[PRG] - type O = Xor[String, ?] :&: List :&: Option :&: Bulb + type O = Either[String, ?] :&: List :&: Option :&: Bulb // ugly head & get :D - val f1: Free[PRG.Cop, Xor[String, String]] = (for { + val f1: Free[PRG.Cop, Either[String, String]] = (for { i <- Bar1(3).freek[PRG].onionT2[O] i <- Bar21(i.head.get).freek[PRG].onionT2[O] i <- Bar22(i.head.get).freek[PRG].onionT2[O] @@ -870,5 +876,298 @@ class AppSpec extends FlatSpec with Matchers { } + "freek" should "special cases 4" in { + sealed trait Foo1[A] + final case class Bar11(s: Int) extends Foo1[Either[String, List[Int]]] + final case class Bar12(s: List[Int]) extends Foo1[Either[String, Option[Int]]] + + sealed trait Foo2[A] + final case class Bar21(s: Int) extends Foo1[Either[Long, Option[List[Int]]]] + final case class Bar22(s: List[Int]) extends Foo1[Either[Long, Option[Int]]] + + type PRG = Foo1 :|: Foo2 :|: NilDSL + val PRG = DSL.Make[PRG] + type O = Either[String, ?] :&: Either[Long, ?] :&: Option :&: Bulb + + val f1: OnionT[Free, PRG.Cop, O, Unit] = for { + l1 <- Bar11(5).freek[PRG].onionX1[O] + _ <- Bar12(l1).freek[PRG].onionT[O] + l2 <- Bar21(6).freek[PRG].onionX2[O] + _ <- Bar22(l2).freek[PRG].onionT[O] + } yield (()) + + } + + "freek" should "extend DSL" in { + object Program { + sealed trait Foo1[A] + final case class Bar11(s: Int) extends Foo1[String] + + sealed trait Foo2[A] + final case class Bar21(s: String) extends Foo2[Int] + + type PRG = Foo1 :|: Foo2 :|: NilDSL + val PRG = DSL.Make[PRG] + + val program = for { + s <- Bar11(5).freek[PRG] + i <- Bar21(s).freek[PRG] + } yield (i) + } + + object OtherProgram { + import Program._ + + sealed trait Foo3[A] + case class Bar31[A](bar11: Foo1[A]) extends Foo3[A] + case class Bar32(i: Int) extends Foo3[String] + + type PRG = Foo3 :|: Foo2 :|: NilDSL + val PRG = DSL.Make[PRG] + + val copknat = CopKNat[Program.PRG.Cop].replace( + new (Foo1 ~> Foo3) { + def apply[A](foo1: Foo1[A]): Foo3[A] = Bar31(foo1) + } + ) + + val program = for { + i <- Program.program.compile(copknat) + s <- Bar32(i).freek[PRG] + } yield (s) + + } + + import Program._ + import OtherProgram._ + + val foo1Future = new (Foo1 ~> Future) { + def apply[A](a: Foo1[A]) = a match { + case Bar11(i) => Future { i.toString } + } + } + + val foo2Future = new (Foo2 ~> Future) { + def apply[A](a: Foo2[A]) = a match { + case Bar21(s) => Future { s.toInt } + } + } + + def foo3Future(foo1Nat: Foo1 ~> Future) = new (Foo3 ~> Future) { + def apply[A](a: Foo3[A]) = a match { + case Bar31(foo1) => foo1Nat(foo1) + case Bar32(i) => Future { i.toString } + } + } + + val interpreter = foo2Future :&: foo3Future(foo1Future) + + val fut = OtherProgram.program.interpret(interpreter) + + () + } + + "freek" should "append DSL" in { + object Program { + sealed trait Foo1[A] + final case class Bar11(s: Int) extends Foo1[String] + + sealed trait Foo2[A] + final case class Bar21(s: String) extends Foo2[Int] + + type PRG = Foo1 :|: Foo2 :|: NilDSL + val PRG = DSL.Make[PRG] + + val program = for { + s <- Bar11(5).freek[PRG] + i <- Bar21(s).freek[PRG] + } yield (i) + } + + object OtherProgram { + import Program._ + + sealed trait Foo3[A] + case class Bar31[A](bar11: Foo1[A]) extends Foo3[A] + case class Bar32(i: Int) extends Foo3[String] + + type PRG = Foo3 :|: Foo2 :|: NilDSL + val PRG = DSL.Make[PRG] + + val copknat = CopKNat[Program.PRG.Cop].replace( + new (Foo1 ~> Foo3) { + def apply[A](foo1: Foo1[A]): Foo3[A] = Bar31(foo1) + } + ) + + val program = for { + i <- Program.program.compile(copknat) + s <- Bar32(i).freek[PRG] + } yield (s) + + } + + import Program._ + import OtherProgram._ + + val foo1Future = new (Foo1 ~> Future) { + def apply[A](a: Foo1[A]) = a match { + case Bar11(i) => Future { i.toString } + } + } + + val foo2Future = new (Foo2 ~> Future) { + def apply[A](a: Foo2[A]) = a match { + case Bar21(s) => Future { s.toInt } + } + } + + def foo3Future(foo1Nat: Foo1 ~> Future) = new (Foo3 ~> Future) { + def apply[A](a: Foo3[A]) = a match { + case Bar31(foo1) => foo1Nat(foo1) + case Bar32(i) => Future { i.toString } + } + } + + val interpreter = foo2Future :&: foo3Future(foo1Future) + + val fut = OtherProgram.program.interpret(interpreter) + + () + } + + "freek" should "transpile" in { + object Program { + sealed trait Foo1[A] + final case class Bar11(s: Int) extends Foo1[Int] + + sealed trait Foo2[A] + final case class Bar21(s: String) extends Foo2[String] + + type PRG = Foo1 :|: Foo2 :|: NilDSL + val PRG = DSL.Make[PRG] + + val program = for { + _ <- Bar11(5).freek[PRG] + _ <- Bar21("1.234").freek[PRG] + } yield (()) + } + + object OtherProgram { + + sealed trait Foo3[A] + final case class Bar31(s: String) extends Foo3[Float] + + sealed trait Foo4[A] + final case class Bar41(s: Float) extends Foo4[String] + + type PRG = Foo3 :|: Foo4 :|: NilDSL + val PRG = DSL.Make[PRG] + + // this is our transpiler transforming a Foo2 into another free program + val transpiler = new (Program.Foo2 ~> Free[PRG.Cop, ?]) { + + def apply[A](f: Program.Foo2[A]): Free[PRG.Cop, A] = f match { + case Program.Bar21(s) => + for { + f <- Bar31(s).freek[PRG] + s <- Bar41(f).freek[PRG] + } yield (s) + } + } + } + + import Program._ + import OtherProgram._ + + // 1/ CopKNat[Program.PRG.Cop] creates a Program.PRG.Cop ~> Program.PRG.Cop + // 2/ .replace creates a natural trans that replaces Program.Foo2 in Program.PRG.Cop by Free[OtherProgram.PRG.Cop, ?] using transpiler + // 3/ The result is a terrible natural transformation (don't try to write that type, it's too ugly, let's scalac do it) : + // (Foo1 :|: Foo2 :|: NilDSL) ~> (Foo1 :|: Free[OtherProgram.PRG.Cop, ?] :|: NilDSL) + val transpileNat = CopKNat[Program.PRG.Cop].replace(OtherProgram.transpiler) + + // Transpile does 2 operations: + // 1/ Replaces Foo2 in Program.PRG.Cop by Free[OtherProgram.PRG.Cop, A] + // -> OtherProgram.transpiler natural transformation converts Foo2 into the free program Free[OtherProgram.PRG.Cop, A] + // -> New PRG.Cop is then Foo1 :|: Free[OtherProgram.PRG.Cop, ?] :|: NilDSL + // + // 2/ Flattens Free[(Foo1 :|: Free[(Foo3 :|: Foo4 :|: NilDSL)#Cop, ?] :|: NilDSL)#Cop, A] into + // Free[(Foo1 :|: Foo3 :|: Foo4 :|: NilDSL)#Cop, A] + val free = Program.program.transpile(transpileNat) + // Same as + // val free2 = Program.f.compile(transpileNat).flatten + + // Write our interpreters for new program (Foo1, Foo3, Foo4) + val foo1Future = new (Foo1 ~> Future) { + def apply[A](a: Foo1[A]) = a match { + case Bar11(i) => Future { i } + } + } + + val foo3Future = new (Foo3 ~> Future) { + def apply[A](a: Foo3[A]) = a match { + case Bar31(s) => Future { s.toFloat } + } + } + + val foo4Future = new (Foo4 ~> Future) { + def apply[A](a: Foo4[A]) = a match { + case Bar41(s) => Future { s.toString } + } + } + + val r = Await.result(free.interpret(foo1Future :&: foo3Future :&: foo4Future), 2.seconds) + println("r:"+r) + () + } + + // "freek" should "special case" in { + // import java.io.File + + // sealed trait KVS[A] + // object KVS { + // case class Get(key: String) extends KVS[String] + // case class Put(key: String, value: String) extends KVS[Unit] + // } + + // sealed trait FileIO[A] + // object FileIO { + // case class Get(name: String) extends FileIO[File] + // case class Delete(name: String) extends FileIO[Unit] + // } + + // val FileInterpreter = new (FileIO ~> Lambda[A => Future[Either[Exception, A]]]) { + // override def apply[A](fa: FileIO[A]): Future[Either[Exception, A]] = fa match { + // case FileIO.Get(name) => + // Future { + // Right(new File(name)) + // } + + // case FileIO.Delete(name) => + // Future { + // new File(name).delete() + // Right(()) + // } + // } + // } + + // val KVSInterpreter = new (KVS ~> Lambda[A => Future[Either[Exception, A]]]) { + // def apply[A](a: KVS[A]): Future[Either[Exception, A]] = a match { + // case KVS.Get(id) => + // Future { + // Right("123") + // } + // case KVS.Put(id, value) => + // Future { + // Right(()) + // } + // } + // } + + + // val interpreter = KVSInterpreter :&: toInterpreter(FileInterpreter) + + // } + } diff --git a/src/test/scala/FreekitSpec.scala b/src/test/scala/FreekitSpec.scala new file mode 100644 index 0000000..f6c37bc --- /dev/null +++ b/src/test/scala/FreekitSpec.scala @@ -0,0 +1,211 @@ +package freek + + +/** + * Copyright 2014 Pascal Voitot (@mandubian) + */ +import org.scalatest._ + +import cats.free.{Free, Trampoline} +import cats.{~>, Id} + +import scala.concurrent._ +import scala.concurrent.duration._ + +// import cats.derived._, functor._, legacy._ +import cats.Functor +import cats.instances.future._ +import cats.instances.option._ +import cats.instances.list._ +import cats.instances.either._ +import ExecutionContext.Implicits.global + +import freek._ + + +class FreekitSpec extends FlatSpec with Matchers { + + "Freek" should "macro" in { + + ////////////////////////////////////////////////////////////////////////// + // LOG DSL + sealed trait Log[A] + object Log { + case class Info(msg: String) extends Log[Unit] + } + + sealed trait Foo1[A] + object Foo1 { + final case class Bar1(a: Int) extends Foo1[Option[Int]] + } + + sealed trait Foo2[A] + object Foo2 { + final case class Bar21(a: Int) extends Foo2[Int] + final case object Bar22 extends Foo2[Int] + } + + + type PRG = Foo1 :|: Foo2 :|: Log :|: NilDSL + val PRG = DSL.Make[PRG] + + object M extends Freekit(PRG) { + val prg = for { + aOpt <- Foo1.Bar1(7) + _ <- Log.Info(s"aOpt:$aOpt") + a <- aOpt match { + case Some(a) => for { + a <- Foo2.Bar21(a) + _ <- Log.Info(s"a1:$a") + } yield (a) + case None => for { + a <- Foo2.Bar22 + _ <- Log.Info(s"a2:$a") + } yield (a) + } + } yield (a) + } + + object MO extends Freekito(PRG) { + type O = Option :&: Bulb + + val prg = for { + a <- Foo1.Bar1(7) + _ <- Log.Info(s"a:$a") + a <- Foo2.Bar21(a) + } yield (a) + } + + val foo1I = new (Foo1 ~> Future) { + import Foo1._ + + def apply[A](f: Foo1[A]): Future[A] = f match { + case Bar1(a) => Future(Some(a)) + + } + } + + val foo2I = new (Foo2 ~> Future) { + import Foo2._ + + def apply[A](f: Foo2[A]): Future[A] = f match { + case Bar21(a) => Future(a) + case Bar22 => Future(0) + } + } + + + val logI = new (Log ~> Future) { + def apply[A](a: Log[A]) = a match { + case Log.Info(msg) => + Future.successful(println(s"[info] $msg")) + } + } + + val f = M.prg.interpret(foo1I :&: foo2I :&: logI) + Await.result(f, 10.seconds) + + val f2 = MO.prg.value.interpret(foo1I :&: foo2I :&: logI) + Await.result(f2, 10.seconds) + } + + + "Freekit" should "special cases 4" in { + sealed trait Foo1[A] + final case class Bar11(s: Int) extends Foo1[Either[String, List[Int]]] + final case class Bar12(s: List[Int]) extends Foo1[Either[String, Option[Int]]] + + sealed trait Foo2[A] + final case class Bar21(s: Int) extends Foo1[Either[Long, Option[List[Int]]]] + final case class Bar22(s: List[Int]) extends Foo1[Either[Long, Option[Int]]] + + type PRG = Foo1 :|: Foo2 :|: NilDSL + val PRG = DSL.Make[PRG] + + object F1 extends Freekito(PRG) { + type O = Either[String, ?] :&: Either[Long, ?] :&: Option :&: Bulb + + val prg = for { + l1 <- Bar11(5).freek[PRG].onionX1[O] + _ <- Bar12(l1) + l2 <- Bar21(6).freek[PRG].onionX2[O] + _ <- Bar22(l2) + } yield (()) + } + } + + "Freekit" should "freek" in { + import Http._ + import DB._ + + object DBService extends Freekit(DSL.Make[Log.DSL :|: DB.DSL :|: NilDSL]) { + + /** the DSL.Make */ + def findById(id: String): Free[PRG.Cop, Either[DBError, Entity]] = + for { + _ <- Log.debug("Searching for entity id:"+id) + res <- FindById(id) + _ <- Log.debug("Search result:"+res) + } yield (res) + } + + object HttpService extends Freekit(DSL.Make[Log.DSL :|: HttpInteract :|: HttpHandle :|: DBService.PRG]) { + + def handle(req: HttpReq): Free[PRG.Cop, HttpResp] = req.url match { + case "/foo" => + for { + _ <- Log.debug("/foo") + dbRes <- DBService.findById("foo").expand[PRG] + + resp <- HttpHandle.result( + dbRes match { + case Left(err) => HttpResp(status = InternalServerError) + case Right(e) => HttpResp(status = Ok, body = e.toString) + } + ) + } yield (resp) + + case _ => HttpHandle.result(HttpResp(status = InternalServerError)) + } + + def serve() : Free[PRG.Cop, Either[RecvError, SendStatus]] = + for { + recv <- HttpInteract.receive() + _ <- Log.info("HttpReceived Request:"+recv) + res <- recv match { + case Left(err) => HttpInteract.stop(Left(err)).freek[PRG] + + case Right(req) => + for { + resp <- handle(req) + _ <- Log.info("Sending Response:"+resp) + ack <- HttpInteract.respond(resp) + res <- if(ack == Ack) serve() + else HttpInteract.stop(Right(ack)).freek[PRG] + } yield (res) + } + } yield (res) + + } + } + "Freek" should "work" in { + import cats._ + import cats.free.Free + import cats.implicits._ + + import freek._ + + object Test { + sealed trait Instruction[T] + // Seq[Int] doesn't represent and error but is the return type of Get + final case class Get() extends Instruction[List[Int]] + + type PRG = Instruction :|: NilDSL + val PRG = freek.DSL.Make[PRG] + type O = Option :&: List :&: Bulb + + Get().freek[PRG].onionT[O] + } + } + +} \ No newline at end of file diff --git a/src/test/scala/LongCompileSpec.scala b/src/test/scala/LongCompileSpec.scala index 647ba9f..5a101fd 100644 --- a/src/test/scala/LongCompileSpec.scala +++ b/src/test/scala/LongCompileSpec.scala @@ -7,7 +7,6 @@ package freek import org.scalatest._ import cats.free.{Free, Trampoline} -import cats.data.Xor import cats.{~>, Id} import scala.concurrent._ @@ -18,6 +17,7 @@ import cats.Functor import cats.instances.future._ import cats.instances.option._ import cats.instances.list._ +import cats.instances.either._ import ExecutionContext.Implicits.global import freek._