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

Rename NaturalTransformation to FunctionK. #1072

Merged
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
31 changes: 31 additions & 0 deletions core/src/main/scala/cats/arrow/FunctionK.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cats
package arrow

import cats.data.{Xor, Coproduct}

trait FunctionK[F[_], G[_]] extends Serializable { self =>
def apply[A](fa: F[A]): G[A]

def compose[E[_]](f: FunctionK[E, F]): FunctionK[E, G] =
new FunctionK[E, G] {
def apply[A](fa: E[A]): G[A] = self.apply(f(fa))
}

def andThen[H[_]](f: FunctionK[G, H]): FunctionK[F, H] =
f.compose(self)

def or[H[_]](h: FunctionK[H,G]): FunctionK[Coproduct[F, H, ?], G] =
new FunctionK[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)
}
}
}

object FunctionK {
def id[F[_]]: FunctionK[F, F] =
new FunctionK[F, F] {
def apply[A](fa: F[A]): F[A] = fa
}
}
31 changes: 0 additions & 31 deletions core/src/main/scala/cats/arrow/NaturalTransformation.scala

This file was deleted.

10 changes: 5 additions & 5 deletions core/src/main/scala/cats/data/Coproduct.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats
package data

import cats.arrow.NaturalTransformation
import cats.arrow.FunctionK
import cats.functor.Contravariant

/** `F` on the left and `G` on the right of [[Xor]].
Expand Down Expand Up @@ -63,13 +63,13 @@ final case class Coproduct[F[_], G[_], A](run: F[A] Xor G[A]) {
*
* Example:
* {{{
* scala> import cats.arrow.NaturalTransformation
* scala> import cats.arrow.FunctionK
* scala> import cats.data.Coproduct
* scala> val listToOption =
* | new NaturalTransformation[List, Option] {
* | new FunctionK[List, Option] {
* | def apply[A](fa: List[A]): Option[A] = fa.headOption
* | }
* scala> val optionToOption = NaturalTransformation.id[Option]
* scala> val optionToOption = FunctionK.id[Option]
* scala> val cp1: Coproduct[List, Option, Int] = Coproduct.leftc(List(1,2,3))
* scala> val cp2: Coproduct[List, Option, Int] = Coproduct.rightc(Some(4))
* scala> cp1.fold(listToOption, optionToOption)
Expand All @@ -78,7 +78,7 @@ final case class Coproduct[F[_], G[_], A](run: F[A] Xor G[A]) {
* res1: Option[Int] = Some(4)
* }}}
*/
def fold[H[_]](f: NaturalTransformation[F, H], g: NaturalTransformation[G, H]): H[A] =
def fold[H[_]](f: FunctionK[F, H], g: FunctionK[G, H]): H[A] =
run.fold(f.apply, g.apply)
}

Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/data/Kleisli.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats
package data

import cats.arrow.{Arrow, Choice, Split, NaturalTransformation}
import cats.arrow.{Arrow, Choice, Split, FunctionK}
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: NaturalTransformation[F,G]): Kleisli[G, A, B] =
def transform[G[_]](f: FunctionK[F,G]): Kleisli[G, A, B] =
Kleisli(a => f(run(a)))

def lower(implicit F: Applicative[F]): Kleisli[F, A, F[B]] =
Expand Down
3 changes: 1 addition & 2 deletions core/src/main/scala/cats/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import cats.data.Xor
*/
package object cats {

type ~>[F[_], G[_]] = arrow.NaturalTransformation[F, G]
type <~[F[_], G[_]] = arrow.NaturalTransformation[G, F]
type ~>[F[_], G[_]] = arrow.FunctionK[F, G]

type ⊥ = Nothing
type ⊤ = Any
Expand Down
10 changes: 5 additions & 5 deletions docs/src/main/tut/freeapplicative.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ at this point. To make our program useful we need to interpret it.

```tut:silent
import cats.Id
import cats.arrow.NaturalTransformation
import cats.arrow.FunctionK
import cats.std.function._

// a function that takes a string as input
type FromString[A] = String => A

val compiler =
new NaturalTransformation[ValidationOp, FromString] {
new FunctionK[ValidationOp, FromString] {
def apply[A](fa: ValidationOp[A]): String => A =
str =>
fa match {
Expand Down Expand Up @@ -103,7 +103,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
type ParValidator[A] = Kleisli[Future, String, A]

val parCompiler =
new NaturalTransformation[ValidationOp, ParValidator] {
new FunctionK[ValidationOp, ParValidator] {
def apply[A](fa: ValidationOp[A]): ParValidator[A] =
Kleisli { str =>
fa match {
Expand All @@ -130,7 +130,7 @@ import cats.std.list._
type Log[A] = Const[List[String], A]

val logCompiler =
new NaturalTransformation[ValidationOp, Log] {
new FunctionK[ValidationOp, Log] {
def apply[A](fa: ValidationOp[A]): Log[A] =
fa match {
case Size(size) => Const(List(s"size >= $size"))
Expand Down Expand Up @@ -166,7 +166,7 @@ import cats.data.Prod
type ValidateAndLog[A] = Prod[ParValidator, Log, A]

val prodCompiler =
new NaturalTransformation[ValidationOp, ValidateAndLog] {
new FunctionK[ValidationOp, ValidateAndLog] {
def apply[A](fa: ValidationOp[A]): ValidateAndLog[A] = {
fa match {
case Size(size) =>
Expand Down
8 changes: 4 additions & 4 deletions docs/src/main/tut/freemonad.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,14 @@ 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
`NaturalTransformation[F,G]` or as done here using the symbolic
`FunctionK[F,G]` or as done here using the symbolic
alternative as `F ~> G`).
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This could then be something like :

To do this, we will use a FunctionK (also known as a natural transformation). A FunctionK is like a function but between type containers instead of values. A FunctionK between types F[_] and G[_] could be written as FunctionK[F, G] (or as F ~> G using the symbolic alternative).

Copy link
Contributor

Choose a reason for hiding this comment

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

I would say that we can describe the type FunctionK as a natural transformation. So, when we would put the term in backticks (i.e. code) we'd say FunctionK but otherwise we'd say transformation or natural transformation. Does this make sense?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That does make sense, yes. It felt weird to use FunctionK in the documentation, that's why I left the documentation itself untouched.


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

```tut:silent
import cats.arrow.NaturalTransformation
import cats.arrow.FunctionK
import cats.{Id, ~>}
import scala.collection.mutable

Expand Down Expand Up @@ -244,7 +244,7 @@ recursive structure by:
This operation is called `Free.foldMap`:

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

`M` must be a `Monad` to be flattenable (the famous monoid aspect
Expand Down Expand Up @@ -366,7 +366,7 @@ def program(implicit I : Interacts[CatsApp], D : DataSource[CatsApp]): Free[Cats
}
```

Finally we write one interpreter per ADT and combine them with a `NaturalTransformation` to `Coproduct` so they can be
Finally we write one interpreter per ADT and combine them with a `FunctionK` to `Coproduct` so they can be
compiled and applied to our `Free` program.

```tut:invisible
Expand Down
4 changes: 2 additions & 2 deletions free/src/main/scala/cats/free/Coyoneda.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats
package free

import cats.arrow.NaturalTransformation
import cats.arrow.FunctionK

/**
* The dual view of the Yoneda lemma. Also a free functor on `F`.
Expand Down Expand Up @@ -38,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: NaturalTransformation[F,G]): Aux[G, A, Pivot] =
final def transform[G[_]](f: FunctionK[F,G]): Aux[G, A, Pivot] =
apply(f(fi))(k)

}
Expand Down
10 changes: 5 additions & 5 deletions free/src/main/scala/cats/free/Free.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package free
import scala.annotation.tailrec

import cats.data.Xor, Xor.{Left, Right}
import cats.arrow.NaturalTransformation
import cats.arrow.FunctionK

object Free {
/**
Expand Down Expand Up @@ -135,7 +135,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: NaturalTransformation[S,M])(implicit M: Monad[M]): M[A] =
final def foldMap[M[_]](f: FunctionK[S,M])(implicit M: Monad[M]): M[A] =
step match {
case Pure(a) => M.pure(a)
case Suspend(s) => f(s)
Expand All @@ -147,13 +147,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: NaturalTransformation[S,T]): Free[T, A] =
final def mapSuspension[T[_]](f: FunctionK[S,T]): Free[T, A] =
foldMap[Free[T, ?]] {
new NaturalTransformation[S, Free[T, ?]] {
new FunctionK[S, Free[T, ?]] {
def apply[B](fa: S[B]): Free[T, B] = Suspend(f(fa))
}
}(Free.freeMonad)

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

}
16 changes: 8 additions & 8 deletions free/src/main/scala/cats/free/FreeApplicative.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats
package free

import cats.arrow.NaturalTransformation
import cats.arrow.FunctionK
import cats.data.Const

/** Applicative Functor for Free */
Expand All @@ -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: NaturalTransformation[F,G])(implicit G: Applicative[G]): G[A] =
final def foldMap[G[_]](f: FunctionK[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 @@ -37,26 +37,26 @@ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable
* Tail recursive only if `F` provides tail recursive interpretation.
*/
final def fold(implicit F: Applicative[F]): F[A] =
foldMap(NaturalTransformation.id[F])
foldMap(FunctionK.id[F])

/** Interpret this algebra into another FreeApplicative */
final def compile[G[_]](f: NaturalTransformation[F,G]): FA[G, A] =
final def compile[G[_]](f: FunctionK[F,G]): FA[G, A] =
foldMap[FA[G, ?]] {
new NaturalTransformation[F, FA[G, ?]] {
new FunctionK[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: NaturalTransformation[F,λ[α => M]]): M =
foldMap[Const[M, ?]](new (NaturalTransformation[F,Const[M, ?]]) {
final def analyze[M:Monoid](f: FunctionK[F,λ[α => M]]): M =
foldMap[Const[M, ?]](new (FunctionK[F,Const[M, ?]]) {
def apply[X](x: F[X]): Const[M,X] = Const(f(x))
}).getConst

/** Compile this FreeApplicative algebra into a Free algebra. */
final def monad: Free[F, A] =
foldMap[Free[F, ?]] {
new NaturalTransformation[F, Free[F, ?]] {
new FunctionK[F, Free[F, ?]] {
def apply[B](fa: F[B]): Free[F, B] = Free.liftF(fa)
}
}
Expand Down
4 changes: 2 additions & 2 deletions free/src/test/scala/cats/free/CoyonedaTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package free

import cats.tests.CatsSuite
import cats.arrow.NaturalTransformation
import cats.arrow.FunctionK
import cats.laws.discipline.{FunctorTests, SerializableTests}

import org.scalacheck.Arbitrary
Expand All @@ -27,7 +27,7 @@ class CoyonedaTests extends CatsSuite {

test("transform and run is same as applying natural trans") {
val nt =
new NaturalTransformation[Option, List] {
new FunctionK[Option, List] {
def apply[A](fa: Option[A]): List[A] = fa.toList
}
val o = Option("hello")
Expand Down
14 changes: 7 additions & 7 deletions free/src/test/scala/cats/free/FreeApplicativeTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package free

import cats.tests.CatsSuite
import cats.arrow.NaturalTransformation
import cats.arrow.FunctionK
import cats.laws.discipline.{CartesianTests, ApplicativeTests, SerializableTests}
import cats.data.State

Expand All @@ -18,7 +18,7 @@ class FreeApplicativeTests extends CatsSuite {
implicit def freeApplicativeEq[S[_]: Applicative, A](implicit SA: Eq[S[A]]): Eq[FreeApplicative[S, A]] =
new Eq[FreeApplicative[S, A]] {
def eqv(a: FreeApplicative[S, A], b: FreeApplicative[S, A]): Boolean = {
val nt = NaturalTransformation.id[S]
val nt = FunctionK.id[S]
SA.eqv(a.foldMap(nt), b.foldMap(nt))
}
}
Expand All @@ -44,7 +44,7 @@ class FreeApplicativeTests extends CatsSuite {
val x = FreeApplicative.lift[Id, Int](1)
val y = FreeApplicative.pure[Id, Int](2)
val f = x.map(i => (j: Int) => i + j)
val nt = NaturalTransformation.id[Id]
val nt = FunctionK.id[Id]
val r1 = y.ap(f)
val r2 = r1.compile(nt)
r1.foldMap(nt) should === (r2.foldMap(nt))
Expand All @@ -57,7 +57,7 @@ class FreeApplicativeTests extends CatsSuite {
val r1 = y.ap(f)
val r2 = r1.monad
val nt =
new NaturalTransformation[Id, Id] {
new FunctionK[Id, Id] {
def apply[A](fa: Id[A]): Id[A] = fa
}
r1.foldMap(nt) should === (r2.foldMap(nt))
Expand All @@ -75,7 +75,7 @@ class FreeApplicativeTests extends CatsSuite {

test("FreeApplicative#analyze") {
type G[A] = List[Int]
val countingNT = new NaturalTransformation[List, G] {
val countingNT = new FunctionK[List, G] {
def apply[A](la: List[A]): G[A] = List(la.length)
}

Expand All @@ -97,7 +97,7 @@ class FreeApplicativeTests extends CatsSuite {

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

val f: NaturalTransformation[Foo,Tracked] = new NaturalTransformation[Foo,Tracked] {
val f: FunctionK[Foo,Tracked] = new FunctionK[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: NaturalTransformation[Id,λ[α => String]] = new NaturalTransformation[Id,λ[α => String]] {
val asString: FunctionK[Id,λ[α => String]] = new FunctionK[Id,λ[α => String]] {
def apply[A](a: A): String = a.toString
}

Expand Down
Loading