Skip to content

Commit

Permalink
Merge pull request #991 from markus1189/natural-transformation
Browse files Browse the repository at this point in the history
Replace  `~>` with `NaturalTransformation`
  • Loading branch information
ceedubs committed May 17, 2016
2 parents 271c197 + dfd04f0 commit defac45
Show file tree
Hide file tree
Showing 11 changed files with 31 additions and 26 deletions.
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/arrow/NaturalTransformation.scala
Expand Up @@ -14,8 +14,8 @@ trait NaturalTransformation[F[_], G[_]] extends Serializable { self =>
def andThen[H[_]](f: NaturalTransformation[G, H]): NaturalTransformation[F, H] =
f.compose(self)

def or[H[_]](h: H ~> G): Coproduct[F, H, ?] ~> G =
new (Coproduct[F, H, ?] ~> G) {
def or[H[_]](h: NaturalTransformation[H,G]): NaturalTransformation[Coproduct[F, H, ?],G] =
new (NaturalTransformation[Coproduct[F, H, ?],G]) {
def apply[A](fa: Coproduct[F, H, A]): G[A] = fa.run match {
case Xor.Left(ff) => self(ff)
case Xor.Right(gg) => h(gg)
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/data/Kleisli.scala
@@ -1,7 +1,7 @@
package cats
package data

import cats.arrow.{Arrow, Choice, Split}
import cats.arrow.{Arrow, Choice, Split, NaturalTransformation}
import cats.functor.{Contravariant, Strong}

/**
Expand Down Expand Up @@ -48,7 +48,7 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { self =>
def local[AA](f: AA => A): Kleisli[F, AA, B] =
Kleisli(f.andThen(run))

def transform[G[_]](f: F ~> G): Kleisli[G, A, B] =
def transform[G[_]](f: NaturalTransformation[F,G]): Kleisli[G, A, B] =
Kleisli(a => f(run(a)))

def lower(implicit F: Applicative[F]): Kleisli[F, A, F[B]] =
Expand Down
7 changes: 5 additions & 2 deletions docs/src/main/tut/freemonad.md
Expand Up @@ -165,12 +165,15 @@ DSL. By itself, this DSL only represents a sequence of operations

To do this, we will use a *natural transformation* between type
containers. Natural transformations go between types like `F[_]` and
`G[_]` (this particular transformation would be written as `F ~> G`).
`G[_]` (this particular transformation would be written as
`NaturalTransformation[F,G]` or as done here using the symbolic
alternative as `F ~> G`).

In our case, we will use a simple mutable map to represent our key
value store:

```tut:silent
import cats.arrow.NaturalTransformation
import cats.{Id, ~>}
import scala.collection.mutable
Expand Down Expand Up @@ -241,7 +244,7 @@ recursive structure by:
This operation is called `Free.foldMap`:

```scala
final def foldMap[M[_]](f: S ~> M)(M: Monad[M]): M[A] = ...
final def foldMap[M[_]](f: NaturalTransformation[S,M])(M: Monad[M]): M[A] = ...
```

`M` must be a `Monad` to be flattenable (the famous monoid aspect
Expand Down
4 changes: 3 additions & 1 deletion free/src/main/scala/cats/free/Coyoneda.scala
@@ -1,6 +1,8 @@
package cats
package free

import cats.arrow.NaturalTransformation

/**
* The dual view of the Yoneda lemma. Also a free functor on `F`.
* This is isomorphic to `F` as long as `F` itself is a functor.
Expand Down Expand Up @@ -36,7 +38,7 @@ sealed abstract class Coyoneda[F[_], A] extends Serializable { self =>
final def map[B](f: A => B): Aux[F, B, Pivot] =
apply(fi)(f compose k)

final def transform[G[_]](f: F ~> G): Aux[G, A, Pivot] =
final def transform[G[_]](f: NaturalTransformation[F,G]): Aux[G, A, Pivot] =
apply(f(fi))(k)

}
Expand Down
7 changes: 3 additions & 4 deletions free/src/main/scala/cats/free/Free.scala
Expand Up @@ -130,7 +130,7 @@ sealed abstract class Free[S[_], A] extends Product with Serializable {
* Run to completion, mapping the suspension with the given transformation at each step and
* accumulating into the monad `M`.
*/
final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] =
final def foldMap[M[_]](f: NaturalTransformation[S,M])(implicit M: Monad[M]): M[A] =
step match {
case Pure(a) => M.pure(a)
case Suspend(s) => f(s)
Expand All @@ -142,14 +142,13 @@ sealed abstract class Free[S[_], A] extends Product with Serializable {
* using the given natural transformation.
* Be careful if your natural transformation is effectful, effects are applied by mapSuspension.
*/
final def mapSuspension[T[_]](f: S ~> T): Free[T, A] =
final def mapSuspension[T[_]](f: NaturalTransformation[S,T]): Free[T, A] =
foldMap[Free[T, ?]] {
new NaturalTransformation[S, Free[T, ?]] {
def apply[B](fa: S[B]): Free[T, B] = Suspend(f(fa))
}
}(Free.freeMonad)

final def compile[T[_]](f: S ~> T): Free[T, A] = mapSuspension(f)
final def compile[T[_]](f: NaturalTransformation[S,T]): Free[T, A] = mapSuspension(f)

}

8 changes: 4 additions & 4 deletions free/src/main/scala/cats/free/FreeApplicative.scala
Expand Up @@ -27,7 +27,7 @@ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable
/** Interprets/Runs the sequence of operations using the semantics of Applicative G
* Tail recursive only if G provides tail recursive interpretation (ie G is FreeMonad)
*/
final def foldMap[G[_]](f: F ~> G)(implicit G: Applicative[G]): G[A] =
final def foldMap[G[_]](f: NaturalTransformation[F,G])(implicit G: Applicative[G]): G[A] =
this match {
case Pure(a) => G.pure(a)
case Ap(pivot, fn) => G.map2(f(pivot), fn.foldMap(f))((a, g) => g(a))
Expand All @@ -40,16 +40,16 @@ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable
foldMap(NaturalTransformation.id[F])

/** Interpret this algebra into another FreeApplicative */
final def compile[G[_]](f: F ~> G): FA[G, A] =
final def compile[G[_]](f: NaturalTransformation[F,G]): FA[G, A] =
foldMap[FA[G, ?]] {
new NaturalTransformation[F, FA[G, ?]] {
def apply[B](fa: F[B]): FA[G, B] = lift(f(fa))
}
}

/** Interpret this algebra into a Monoid */
final def analyze[M:Monoid](f: F ~> λ[α => M]): M =
foldMap[Const[M, ?]](new (F ~> Const[M, ?]) {
final def analyze[M:Monoid](f: NaturalTransformation[F,λ[α => M]]): M =
foldMap[Const[M, ?]](new (NaturalTransformation[F,Const[M, ?]]) {
def apply[X](x: F[X]): Const[M,X] = Const(f(x))
}).getConst

Expand Down
4 changes: 2 additions & 2 deletions free/src/test/scala/cats/free/FreeApplicativeTests.scala
Expand Up @@ -97,7 +97,7 @@ class FreeApplicativeTests extends CatsSuite {

type Tracked[A] = State[String, A]

val f: Foo ~> Tracked = new (Foo ~> Tracked) {
val f: NaturalTransformation[Foo,Tracked] = new NaturalTransformation[Foo,Tracked] {
def apply[A](fa: Foo[A]): Tracked[A] = State[String, A]{ s0 =>
(s0 + fa.toString + ";", fa.getA)
}
Expand All @@ -120,7 +120,7 @@ class FreeApplicativeTests extends CatsSuite {

val z = Apply[Dsl].map2(x, y)((_, _) => ())

val asString: Id ~> λ[α => String] = new (Id ~> λ[α => String]) {
val asString: NaturalTransformation[Id,λ[α => String]] = new NaturalTransformation[Id,λ[α => String]] {
def apply[A](a: A): String = a.toString
}

Expand Down
4 changes: 2 additions & 2 deletions free/src/test/scala/cats/free/FreeTests.scala
Expand Up @@ -56,7 +56,7 @@ class FreeTests extends CatsSuite {
z <- if (j<10000) a(j) else Free.pure[FTestApi, Int](j)
} yield z

def runner: FTestApi ~> Id = new (FTestApi ~> Id) {
def runner: NaturalTransformation[FTestApi,Id] = new NaturalTransformation[FTestApi,Id] {
def apply[A](fa: FTestApi[A]): Id[A] = fa match {
case TB(i) => i+1
}
Expand All @@ -77,7 +77,7 @@ object FreeTests extends FreeTestsInstances {
}

sealed trait FreeTestsInstances {
val headOptionU: List ~> Option = new (List ~> Option) {
val headOptionU: NaturalTransformation[List,Option] = new NaturalTransformation[List,Option] {
def apply[A](fa: List[A]): Option[A] = fa.headOption
}

Expand Down
7 changes: 4 additions & 3 deletions free/src/test/scala/cats/free/InjectTests.scala
@@ -1,6 +1,7 @@
package cats
package free

import cats.arrow.NaturalTransformation
import cats.tests.CatsSuite
import cats.data.{Xor, Coproduct}
import org.scalacheck._
Expand Down Expand Up @@ -39,19 +40,19 @@ class InjectTests extends CatsSuite {
implicit def test2Arbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test2[A]] =
Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test2(s, f))

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

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

val coProductInterpreter: T ~> Id = Test1Interpreter or Test2Interpreter
val coProductInterpreter: NaturalTransformation[T,Id] = Test1Interpreter or Test2Interpreter

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

Expand Down
4 changes: 2 additions & 2 deletions tests/src/test/scala/cats/tests/KleisliTests.scala
@@ -1,7 +1,7 @@
package cats
package tests

import cats.arrow.{Arrow, Choice, Split}
import cats.arrow.{Arrow, Choice, Split, NaturalTransformation}
import cats.data.{XorT, Kleisli, Reader}
import cats.functor.{Contravariant, Strong}
import cats.laws.discipline._
Expand Down Expand Up @@ -126,7 +126,7 @@ class KleisliTests extends CatsSuite {

test("transform") {
val opt = Kleisli { (x: Int) => Option(x.toDouble) }
val optToList = new (Option ~> List) { def apply[A](fa: Option[A]): List[A] = fa.toList }
val optToList = new NaturalTransformation[Option,List] { def apply[A](fa: Option[A]): List[A] = fa.toList }
val list = opt.transform(optToList)

val is = 0.to(10).toList
Expand Down
Expand Up @@ -28,11 +28,11 @@ class NaturalTransformationTests extends CatsSuite {

case class Test2[A](v : A) extends Test2Algebra[A]

object Test1NT extends (Test1Algebra ~> Id) {
object Test1NT extends NaturalTransformation[Test1Algebra,Id] {
override def apply[A](fa: Test1Algebra[A]): Id[A] = fa.v
}

object Test2NT extends (Test2Algebra ~> Id) {
object Test2NT extends NaturalTransformation[Test2Algebra,Id] {
override def apply[A](fa: Test2Algebra[A]): Id[A] = fa.v
}

Expand Down

0 comments on commit defac45

Please sign in to comment.