Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to Inject. #1557

Merged
merged 5 commits into from
Mar 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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[α]]]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we call these inject and project ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m fine with either. The current ones match the paper and Scalaz. Also, Matryoshka defines project, so I don’t mind the lack of collision 😁


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