Skip to content

Commit

Permalink
Improvements to Inject. (#1557)
Browse files Browse the repository at this point in the history
* Improvements to Inject.

- move it back to cats-core (near `Coproduct`)
- make `inj`/`prj` natural transformations
- add `apply`/`unapply` (lowered `inj`/`prj`)
- define `Free.roll`
- move `injectRoll` (née `Inject.inject`) and `match_` to `Free`
- reintroduce "null identity" test (written by @edmundnoble)

* Fix tut breakage, and remove commented import.

* Remove an unused import.

* Change to not require syntax.either.

Otherwise we need it for 2.10 & 2.11 and need to _not_ have it for 2.12.

* Add tests for Inject#apply and #unapply.
  • Loading branch information
sellout authored and kailuowang committed Mar 20, 2017
1 parent f6ef91e commit 8fbf546
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 163 deletions.
46 changes: 46 additions & 0 deletions core/src/main/scala/cats/Inject.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cats

import cats.arrow.FunctionK
import cats.data.Coproduct

/**
* Inject type class as described in "Data types a la carte" (Swierstra 2008).
*
* @see [[http://www.staff.science.uu.nl/~swier004/publications/2008-jfp.pdf]]
*/
sealed abstract class Inject[F[_], G[_]] {
def inj: FunctionK[F, G]

def prj: FunctionK[G, λ[α => Option[F[α]]]]

def apply[A](fa: F[A]): G[A] = inj(fa)

def unapply[A](ga: G[A]): Option[F[A]] = prj(ga)
}

private[cats] sealed abstract class InjectInstances {
implicit def catsReflexiveInjectInstance[F[_]]: Inject[F, F] =
new Inject[F, F] {
val inj = λ[FunctionK[F, F]](identity(_))

val prj = λ[FunctionK[F, λ[α => Option[F[α]]]]](Some(_))
}

implicit def catsLeftInjectInstance[F[_], G[_]]: Inject[F, Coproduct[F, G, ?]] =
new Inject[F, Coproduct[F, G, ?]] {
val inj = λ[FunctionK[F, Coproduct[F, G, ?]]](Coproduct.leftc(_))

val prj = λ[FunctionK[Coproduct[F, G, ?], λ[α => Option[F[α]]]]](_.run.left.toOption)
}

implicit def catsRightInjectInstance[F[_], G[_], H[_]](implicit I: Inject[F, G]): Inject[F, Coproduct[H, G, ?]] =
new Inject[F, Coproduct[H, G, ?]] {
val inj = λ[FunctionK[G, Coproduct[H, G, ?]]](Coproduct.rightc(_)) compose I.inj

val prj = λ[FunctionK[Coproduct[H, G, ?], λ[α => Option[F[α]]]]](_.run.right.toOption.flatMap(I.prj(_)))
}
}

object Inject extends InjectInstances {
def apply[F[_], G[_]](implicit I: Inject[F, G]): Inject[F, G] = I
}
5 changes: 5 additions & 0 deletions core/src/main/scala/cats/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ package object cats {
type = Nothing
type = Any

/** [[cats.Inject]][F, G] */
type :<:[F[_], G[_]] = Inject[F, G]

/** [[cats.Inject]][F, G] */
type :≺:[F[_], G[_]] = Inject[F, G]

/**
* Identity, encoded as `type Id[A] = A`, a convenient alias to make
Expand Down
4 changes: 2 additions & 2 deletions docs/src/main/tut/datatypes/freemonad.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,8 @@ Let's see a trivial example of unrelated ADT's getting composed as a `Coproduct`

```tut:silent
import cats.data.Coproduct
import cats.free.{Inject, Free}
import cats.{Id, ~>}
import cats.free.Free
import cats.{Id, Inject, ~>}
import scala.collection.mutable.ListBuffer
```

Expand Down
12 changes: 12 additions & 0 deletions free/src/main/scala/cats/free/Free.scala
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ object Free {
*/
def liftF[F[_], A](value: F[A]): Free[F, A] = Suspend(value)

/**
* Absorb a step into the free monad.
*/
def roll[F[_], A](value: F[Free[F, A]]): Free[F, A] =
liftF(value).flatMap(identity)

/**
* Suspend the creation of a `Free[F, A]` value.
*/
Expand Down Expand Up @@ -221,6 +227,12 @@ object Free {
Free.liftF(I.inj(fa))
}

def injectRoll[F[_], G[_], A](ga: G[Free[F, A]])(implicit I: Inject[G, F]): Free[F, A] =
Free.roll(I.inj(ga))

def match_[F[_], G[_], A](fa: Free[F, A])(implicit F: Functor[F], I: Inject[G, F]): Option[G[Free[F, A]]] =
fa.resume.fold(I.prj(_), _ => None)

/**
* `Free[S, ?]` has a monad for any type constructor `S[_]`.
*/
Expand Down
49 changes: 0 additions & 49 deletions free/src/main/scala/cats/free/Inject.scala

This file was deleted.

7 changes: 0 additions & 7 deletions free/src/main/scala/cats/free/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,4 @@ package object free {
/** Alias for the free monad over the `Function0` functor. */
type Trampoline[A] = Free[Function0, A]
object Trampoline extends TrampolineFunctions

/** [[cats.free.Inject]][F, G] */
type :<:[F[_], G[_]] = Inject[F, G]

/** [[cats.free.Inject]][F, G] */
type :≺:[F[_], G[_]] = Inject[F, G]

}
86 changes: 85 additions & 1 deletion free/src/test/scala/cats/free/FreeTests.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package cats
package free

import cats.tests.CatsSuite
import cats.arrow.FunctionK
import cats.data.Coproduct
import cats.laws.discipline.{CartesianTests, MonadTests, SerializableTests}
import cats.laws.discipline.arbitrary.catsLawsArbitraryForFn0
import cats.tests.CatsSuite

import org.scalacheck.{Arbitrary, Gen, Cogen}
import Arbitrary.arbFunction1
Expand Down Expand Up @@ -107,6 +108,89 @@ class FreeTests extends CatsSuite {
}
assert(res == Either.left(100000))
}

sealed trait Test1Algebra[A]

case class Test1[A](value : Int, f: Int => A) extends Test1Algebra[A]

object Test1Algebra {
implicit def test1AlgebraAFunctor: Functor[Test1Algebra] =
new Functor[Test1Algebra] {
def map[A, B](a: Test1Algebra[A])(f: A => B): Test1Algebra[B] = a match {
case Test1(k, h) => Test1(k, x => f(h(x)))
}
}

implicit def test1AlgebraArbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test1Algebra[A]] =
Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test1(s, f))
}

sealed trait Test2Algebra[A]

case class Test2[A](value : Int, f: Int => A) extends Test2Algebra[A]

object Test2Algebra {
implicit def test2AlgebraAFunctor: Functor[Test2Algebra] =
new Functor[Test2Algebra] {
def map[A, B](a: Test2Algebra[A])(f: A => B): Test2Algebra[B] = a match {
case Test2(k, h) => Test2(k, x => f(h(x)))
}
}

implicit def test2AlgebraArbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test2Algebra[A]] =
Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test2(s, f))
}

type T[A] = Coproduct[Test1Algebra, Test2Algebra, A]

object Test1Interpreter extends FunctionK[Test1Algebra,Id] {
override def apply[A](fa: Test1Algebra[A]): Id[A] = fa match {
case Test1(k, h) => h(k)
}
}

object Test2Interpreter extends FunctionK[Test2Algebra,Id] {
override def apply[A](fa: Test2Algebra[A]): Id[A] = fa match {
case Test2(k, h) => h(k)
}
}

val coProductInterpreter: FunctionK[T,Id] = Test1Interpreter or Test2Interpreter

test(".inject") {
forAll { (x: Int, y: Int) =>
def res[F[_]]
(implicit I0: Test1Algebra :<: F,
I1: Test2Algebra :<: F): Free[F, Int] = {
for {
a <- Free.inject[Test1Algebra, F](Test1(x, identity))
b <- Free.inject[Test2Algebra, F](Test2(y, identity))
} yield a + b
}
(res[T] foldMap coProductInterpreter) == (x + y) should ===(true)
}
}

val x: Free[T, Int] = Free.inject[Test1Algebra, T](Test1(1, identity))

test(".injectRoll") {
def distr[F[_], A](f: Free[F, A])
(implicit
F: Functor[F],
I0: Test1Algebra :<: F,
I1: Test2Algebra :<: F): Option[Free[F, A]] =
for {
Test1(x, h) <- Free.match_[F, Test1Algebra, A](f)
Test2(y, k) <- Free.match_[F, Test2Algebra, A](h(x))
} yield k(x + y)

forAll { (x: Int, y: Int) =>
val expr1: Free[T, Int] = Free.injectRoll[T, Test1Algebra, Int](Test1(x, Free.pure))
val expr2: Free[T, Int] = Free.injectRoll[T, Test2Algebra, Int](Test2(y, Free.pure))
val res = distr[T, Int](expr1 >> expr2)
res == Some(Free.pure(x + y)) should ===(true)
}
}
}

object FreeTests extends FreeTestsInstances {
Expand Down
104 changes: 0 additions & 104 deletions free/src/test/scala/cats/free/InjectTests.scala

This file was deleted.

Loading

0 comments on commit 8fbf546

Please sign in to comment.