Skip to content

Commit 29ffa90

Browse files
committed
Merge pull request #671 from 47deg/master
Request to Include `cats.data.Coproduct` and `cats.free.Inject`
2 parents fd1a45b + 5ea5313 commit 29ffa90

12 files changed

Lines changed: 606 additions & 0 deletions

File tree

core/src/main/scala/cats/arrow/NaturalTransformation.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package cats
22
package arrow
33

4+
import cats.data.{Xor, Coproduct}
5+
46
trait NaturalTransformation[F[_], G[_]] extends Serializable { self =>
57
def apply[A](fa: F[A]): G[A]
68

@@ -18,4 +20,12 @@ object NaturalTransformation {
1820
new NaturalTransformation[F, F] {
1921
def apply[A](fa: F[A]): F[A] = fa
2022
}
23+
24+
def or[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): Coproduct[F, G, ?] ~> H =
25+
new (Coproduct[F, G, ?] ~> H) {
26+
def apply[A](fa: Coproduct[F, G, A]): H[A] = fa.run match {
27+
case Xor.Left(ff) => f(ff)
28+
case Xor.Right(gg) => g(gg)
29+
}
30+
}
2131
}
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package cats
2+
package data
3+
4+
import cats.functor.Contravariant
5+
6+
/** `F` on the left and `G` on the right of [[Xor]].
7+
*
8+
* @param run The underlying [[Xor]]. */
9+
final case class Coproduct[F[_], G[_], A](run: F[A] Xor G[A]) {
10+
11+
import Coproduct._
12+
13+
def map[B](f: A => B)(implicit F: Functor[F], G: Functor[G]): Coproduct[F, G, B] =
14+
Coproduct(run.bimap(F.lift(f), G.lift(f)))
15+
16+
def coflatMap[B](f: Coproduct[F, G, A] => B)(implicit F: CoflatMap[F], G: CoflatMap[G]): Coproduct[F, G, B] =
17+
Coproduct(
18+
run.bimap(a => F.coflatMap(a)(x => f(leftc(x))), a => G.coflatMap(a)(x => f(rightc(x))))
19+
)
20+
21+
def duplicate(implicit F: CoflatMap[F], G: CoflatMap[G]): Coproduct[F, G, Coproduct[F, G, A]] =
22+
Coproduct(run.bimap(
23+
x => F.coflatMap(x)(a => leftc(a))
24+
, x => G.coflatMap(x)(a => rightc(a)))
25+
)
26+
27+
def extract(implicit F: Comonad[F], G: Comonad[G]): A =
28+
run.fold(F.extract, G.extract)
29+
30+
def contramap[B](f: B => A)(implicit F: Contravariant[F], G: Contravariant[G]): Coproduct[F, G, B] =
31+
Coproduct(run.bimap(F.contramap(_)(f), G.contramap(_)(f)))
32+
33+
def foldRight[B](z: Eval[B])(f: (A, Eval[B]) => Eval[B])(implicit F: Foldable[F], G: Foldable[G]): Eval[B] =
34+
run.fold(a => F.foldRight(a, z)(f), a => G.foldRight(a, z)(f))
35+
36+
def foldLeft[B](z: B)(f: (B, A) => B)(implicit F: Foldable[F], G: Foldable[G]): B =
37+
run.fold(a => F.foldLeft(a, z)(f), a => G.foldLeft(a, z)(f))
38+
39+
def foldMap[B](f: A => B)(implicit F: Foldable[F], G: Foldable[G], M: Monoid[B]): B =
40+
run.fold(F.foldMap(_)(f), G.foldMap(_)(f))
41+
42+
def traverse[X[_], B](g: A => X[B])(implicit F: Traverse[F], G: Traverse[G], A: Applicative[X]): X[Coproduct[F, G, B]] =
43+
run.fold(
44+
x => A.map(F.traverse(x)(g))(leftc(_))
45+
, x => A.map(G.traverse(x)(g))(rightc(_))
46+
)
47+
48+
def isLeft: Boolean =
49+
run.isLeft
50+
51+
def isRight: Boolean =
52+
run.isRight
53+
54+
def swap: Coproduct[G, F, A] =
55+
Coproduct(run.swap)
56+
57+
def toValidated: Validated[F[A], G[A]] =
58+
run.toValidated
59+
60+
}
61+
62+
object Coproduct extends CoproductInstances {
63+
64+
def leftc[F[_], G[_], A](x: F[A]): Coproduct[F, G, A] =
65+
Coproduct(Xor.left(x))
66+
67+
def rightc[F[_], G[_], A](x: G[A]): Coproduct[F, G, A] =
68+
Coproduct(Xor.right(x))
69+
70+
final class CoproductLeft[G[_]] private[Coproduct] {
71+
def apply[F[_], A](fa: F[A]): Coproduct[F, G, A] = Coproduct(Xor.left(fa))
72+
}
73+
74+
final class CoproductRight[F[_]] private[Coproduct] {
75+
def apply[G[_], A](ga: G[A]): Coproduct[F, G, A] = Coproduct(Xor.right(ga))
76+
}
77+
78+
def left[G[_]]: CoproductLeft[G] = new CoproductLeft[G]
79+
80+
def right[F[_]]: CoproductRight[F] = new CoproductRight[F]
81+
82+
}
83+
84+
private[data] sealed abstract class CoproductInstances3 {
85+
86+
implicit def coproductEq[F[_], G[_], A](implicit E: Eq[F[A] Xor G[A]]): Eq[Coproduct[F, G, A]] =
87+
Eq.by(_.run)
88+
89+
implicit def coproductFunctor[F[_], G[_]](implicit F0: Functor[F], G0: Functor[G]): Functor[Coproduct[F, G, ?]] =
90+
new CoproductFunctor[F, G] {
91+
implicit def F: Functor[F] = F0
92+
93+
implicit def G: Functor[G] = G0
94+
}
95+
96+
implicit def coproductFoldable[F[_], G[_]](implicit F0: Foldable[F], G0: Foldable[G]): Foldable[Coproduct[F, G, ?]] =
97+
new CoproductFoldable[F, G] {
98+
implicit def F: Foldable[F] = F0
99+
100+
implicit def G: Foldable[G] = G0
101+
}
102+
}
103+
104+
private[data] sealed abstract class CoproductInstances2 extends CoproductInstances3 {
105+
106+
implicit def coproductContravariant[F[_], G[_]](implicit F0: Contravariant[F], G0: Contravariant[G]): Contravariant[Coproduct[F, G, ?]] =
107+
new CoproductContravariant[F, G] {
108+
implicit def F: Contravariant[F] = F0
109+
110+
implicit def G: Contravariant[G] = G0
111+
}
112+
}
113+
114+
private[data] sealed abstract class CoproductInstances1 extends CoproductInstances2 {
115+
implicit def coproductCoflatMap[F[_], G[_]](implicit F0: CoflatMap[F], G0: CoflatMap[G]): CoflatMap[Coproduct[F, G, ?]] =
116+
new CoproductCoflatMap[F, G] {
117+
implicit def F: CoflatMap[F] = F0
118+
119+
implicit def G: CoflatMap[G] = G0
120+
}
121+
}
122+
123+
private[data] sealed abstract class CoproductInstances0 extends CoproductInstances1 {
124+
implicit def coproductTraverse[F[_], G[_]](implicit F0: Traverse[F], G0: Traverse[G]): Traverse[Coproduct[F, G, ?]] =
125+
new CoproductTraverse[F, G] {
126+
implicit def F: Traverse[F] = F0
127+
128+
implicit def G: Traverse[G] = G0
129+
}
130+
}
131+
132+
sealed abstract class CoproductInstances extends CoproductInstances0 {
133+
134+
implicit def coproductComonad[F[_], G[_]](implicit F0: Comonad[F], G0: Comonad[G]): Comonad[Coproduct[F, G, ?]] =
135+
new CoproductComonad[F, G] {
136+
implicit def F: Comonad[F] = F0
137+
138+
implicit def G: Comonad[G] = G0
139+
}
140+
}
141+
142+
private[data] trait CoproductFunctor[F[_], G[_]] extends Functor[Coproduct[F, G, ?]] {
143+
implicit def F: Functor[F]
144+
145+
implicit def G: Functor[G]
146+
147+
def map[A, B](a: Coproduct[F, G, A])(f: A => B): Coproduct[F, G, B] =
148+
a map f
149+
}
150+
151+
private[data] trait CoproductContravariant[F[_], G[_]] extends Contravariant[Coproduct[F, G, ?]] {
152+
implicit def F: Contravariant[F]
153+
154+
implicit def G: Contravariant[G]
155+
156+
def contramap[A, B](a: Coproduct[F, G, A])(f: B => A): Coproduct[F, G, B] =
157+
a contramap f
158+
}
159+
160+
private[data] trait CoproductFoldable[F[_], G[_]] extends Foldable[Coproduct[F, G, ?]] {
161+
implicit def F: Foldable[F]
162+
163+
implicit def G: Foldable[G]
164+
165+
def foldRight[A, B](fa: Coproduct[F, G, A], z: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
166+
fa.foldRight(z)(f)
167+
168+
def foldLeft[A, B](fa: Coproduct[F, G, A], z: B)(f: (B, A) => B): B =
169+
fa.foldLeft(z)(f)
170+
171+
override def foldMap[A, B](fa: Coproduct[F, G, A])(f: A => B)(implicit M: Monoid[B]): B =
172+
fa foldMap f
173+
}
174+
175+
private[data] trait CoproductTraverse[F[_], G[_]] extends CoproductFoldable[F, G] with Traverse[Coproduct[F, G, ?]] {
176+
implicit def F: Traverse[F]
177+
178+
implicit def G: Traverse[G]
179+
180+
override def map[A, B](a: Coproduct[F, G, A])(f: A => B): Coproduct[F, G, B] =
181+
a map f
182+
183+
override def traverse[X[_] : Applicative, A, B](fa: Coproduct[F, G, A])(f: A => X[B]): X[Coproduct[F, G, B]] =
184+
fa traverse f
185+
}
186+
187+
private[data] trait CoproductCoflatMap[F[_], G[_]] extends CoflatMap[Coproduct[F, G, ?]] {
188+
implicit def F: CoflatMap[F]
189+
190+
implicit def G: CoflatMap[G]
191+
192+
def map[A, B](a: Coproduct[F, G, A])(f: A => B): Coproduct[F, G, B] =
193+
a map f
194+
195+
def coflatMap[A, B](a: Coproduct[F, G, A])(f: Coproduct[F, G, A] => B): Coproduct[F, G, B] =
196+
a coflatMap f
197+
198+
}
199+
200+
private[data] trait CoproductComonad[F[_], G[_]] extends Comonad[Coproduct[F, G, ?]] {
201+
implicit def F: Comonad[F]
202+
203+
implicit def G: Comonad[G]
204+
205+
def map[A, B](a: Coproduct[F, G, A])(f: A => B): Coproduct[F, G, B] =
206+
a map f
207+
208+
def extract[A](p: Coproduct[F, G, A]): A =
209+
p.extract
210+
211+
def coflatMap[A, B](a: Coproduct[F, G, A])(f: Coproduct[F, G, A] => B): Coproduct[F, G, B] =
212+
a coflatMap f
213+
214+
def duplicate[A](a: Coproduct[F, G, A]): Coproduct[F, G, Coproduct[F, G, A]] =
215+
a.duplicate
216+
}
217+

core/src/main/scala/cats/syntax/all.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ trait AllSyntax
3232
with TraverseSyntax
3333
with XorSyntax
3434
with ValidatedSyntax
35+
with CoproductSyntax
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package cats
2+
package syntax
3+
4+
import cats.data.Coproduct
5+
6+
trait CoproductSyntax {
7+
implicit def coproductSyntax[F[_], A](a: F[A]): CoproductOps[F, A] = new CoproductOps(a)
8+
}
9+
10+
final class CoproductOps[F[_], A](val a: F[A]) extends AnyVal {
11+
def leftc[G[_]]: Coproduct[F, G, A] = Coproduct.leftc(a)
12+
def rightc[G[_]]: Coproduct[G, F, A] = Coproduct.rightc(a)
13+
}

docs/src/main/tut/freemonad.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,109 @@ it's not too hard to get around.)
348348
val result: Map[String, Int] = compilePure(program, Map.empty)
349349
```
350350

351+
## Composing Free monads ADTs.
352+
353+
Real world applications often time combine different algebras.
354+
The `Inject` typeclass described by Swierstra in [Data types à la carte](http://www.staff.science.uu.nl/~swier004/Publications/DataTypesALaCarte.pdf)
355+
lets us compose different algebras in the context of `Free`.
356+
357+
Let's see a trivial example of unrelated ADT's getting composed as a `Coproduct` that can form a more complex program.
358+
359+
```tut
360+
import cats.arrow.NaturalTransformation
361+
import cats.data.{Xor, Coproduct}
362+
import cats.free.{Inject, Free}
363+
import cats.{Id, ~>}
364+
import scala.collection.mutable.ListBuffer
365+
```
366+
367+
```tut
368+
/* Handles user interaction */
369+
sealed trait Interact[A]
370+
case class Ask(prompt: String) extends Interact[String]
371+
case class Tell(msg: String) extends Interact[Unit]
372+
373+
/* Represents persistence operations */
374+
sealed trait DataOp[A]
375+
case class AddCat(a: String) extends DataOp[Unit]
376+
case class GetAllCats() extends DataOp[List[String]]
377+
```
378+
379+
Once the ADTs are defined we can formally state that a `Free` program is the Coproduct of it's Algebras.
380+
381+
```tut
382+
type CatsApp[A] = Coproduct[DataOp, Interact, A]
383+
```
384+
385+
In order to take advantage of monadic composition we use smart constructors to lift our Algebra to the `Free` context.
386+
387+
```tut
388+
class Interacts[F[_]](implicit I: Inject[Interact, F]) {
389+
def tell(msg: String): Free[F, Unit] = Free.inject[Interact, F](Tell(msg))
390+
def ask(prompt: String): Free[F, String] = Free.inject[Interact, F](Ask(prompt))
391+
}
392+
393+
object Interacts {
394+
implicit def interacts[F[_]](implicit I: Inject[Interact, F]): Interacts[F] = new Interacts[F]
395+
}
396+
397+
class DataSource[F[_]](implicit I: Inject[DataOp, F]) {
398+
def addCat(a: String): Free[F, Unit] = Free.inject[DataOp, F](AddCat(a))
399+
def getAllCats: Free[F, List[String]] = Free.inject[DataOp, F](GetAllCats())
400+
}
401+
402+
object DataSource {
403+
implicit def dataSource[F[_]](implicit I: Inject[DataOp, F]): DataSource[F] = new DataSource[F]
404+
}
405+
```
406+
407+
ADTs are now easily composed and trivially intertwined inside monadic contexts.
408+
409+
```tut
410+
def program(implicit I : Interacts[CatsApp], D : DataSource[CatsApp]) = {
411+
412+
import I._, D._
413+
414+
for {
415+
cat <- ask("What's the kitty's name")
416+
_ <- addCat(cat)
417+
cats <- getAllCats
418+
_ <- tell(cats.toString)
419+
} yield ()
420+
}
421+
```
422+
423+
Finally we write one interpreter per ADT and combine them with a `NaturalTransformation` to `Coproduct` so they can be
424+
compiled and applied to our `Free` program.
425+
426+
```scala
427+
object ConsoleCatsInterpreter extends (Interact ~> Id) {
428+
def apply[A](i: Interact[A]) = i match {
429+
case Ask(prompt) =>
430+
println(prompt)
431+
scala.io.StdIn.readLine()
432+
case Tell(msg) =>
433+
println(msg)
434+
}
435+
}
436+
437+
object InMemoryDatasourceInterpreter extends (DataOp ~> Id) {
438+
439+
private[this] val memDataSet = new ListBuffer[String]
440+
441+
def apply[A](fa: DataOp[A]) = fa match {
442+
case AddCat(a) => memDataSet.append(a); ()
443+
case GetAllCats() => memDataSet.toList
444+
}
445+
}
446+
447+
val interpreter: CatsApp ~> Id = NaturalTransformation.or(InMemoryDatasourceInterpreter, ConsoleCatsInterpreter)
448+
449+
import DataSource._, Interacts._
450+
451+
val evaled = program.foldMap(interpreter)
452+
```
453+
351454
## <a name="what-is-free-in-theory"></a>For the curious ones: what is Free in theory?
352455

353456
Mathematically-speaking, a *free monad* (at least in the programming

free/src/main/scala/cats/free/Free.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ object Free {
3030
/** Lift a pure value into Free */
3131
def pure[S[_], A](a: A): Free[S, A] = Pure(a)
3232

33+
final class FreeInjectPartiallyApplied[F[_], G[_]] private[free] {
34+
def apply[A](fa: F[A])(implicit I : Inject[F, G]): Free[G, A] =
35+
Free.liftF(I.inj(fa))
36+
}
37+
38+
def inject[F[_], G[_]]: FreeInjectPartiallyApplied[F, G] = new FreeInjectPartiallyApplied
39+
3340
/**
3441
* `Free[S, ?]` has a monad for any type constructor `S[_]`.
3542
*/

0 commit comments

Comments
 (0)