-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Removed Cofree[List, A] tests and added attribution Remove unused imports Add Reducible instance, separate tests, fix Traverse1 comment Removed unfoldStrategy, added a test for unfold
- Loading branch information
1 parent
91242d0
commit 85f6852
Showing
2 changed files
with
216 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package cats | ||
package free | ||
|
||
/** | ||
* A free comonad for some branching functor `S`. Branching is done lazily using Eval. | ||
* A tree with data at the branches, as opposed to Free which is a tree with data at the leaves. | ||
* Not an instruction set functor made into a program monad as in Free, but an instruction set's outputs as a | ||
* functor made into a tree of the possible worlds reachable using the instruction set. | ||
* | ||
* This Scala implementation of `Cofree` and its usages are derived from | ||
* [[https://github.com/scalaz/scalaz/blob/series/7.3.x/core/src/main/scala/scalaz/Cofree.scala Scalaz's Cofree]], | ||
* originally written by Rúnar Bjarnason. | ||
*/ | ||
final case class Cofree[S[_], A](head: A, tailEval: Eval[S[Cofree[S, A]]]) { | ||
|
||
/** Evaluates and returns the tail of the computation. */ | ||
def tailForced: S[Cofree[S, A]] = tailEval.value | ||
|
||
/** Applies `f` to the head and `g` to the tail. */ | ||
def transform[B](f: A => B, g: Cofree[S, A] => Cofree[S, B])(implicit S: Functor[S]): Cofree[S, B] = | ||
Cofree[S, B](f(head), tailEval.map(S.map(_)(g))) | ||
|
||
/** Map over head and inner `S[_]` branches. */ | ||
def map[B](f: A => B)(implicit S: Functor[S]): Cofree[S, B] = | ||
transform(f, _.map(f)) | ||
|
||
/** Transform the branching functor at the root of the Cofree tree. */ | ||
def mapBranchingRoot(nat: S ~> S)(implicit S: Functor[S]): Cofree[S, A] = | ||
Cofree[S, A](head, tailEval.map(nat(_))) | ||
|
||
/** Transform the branching functor, using the S functor to perform the recursion. */ | ||
def mapBranchingS[T[_]](nat: S ~> T)(implicit S: Functor[S]): Cofree[T, A] = | ||
Cofree[T, A](head, tailEval.map(v => nat(S.map(v)(_.mapBranchingS(nat))))) | ||
|
||
/** Transform the branching functor, using the T functor to perform the recursion. */ | ||
def mapBranchingT[T[_]](nat: S ~> T)(implicit T: Functor[T]): Cofree[T, A] = | ||
Cofree[T, A](head, tailEval.map(v => T.map(nat(v))(_.mapBranchingT(nat)))) | ||
|
||
/** Map `f` over each subtree of the computation. */ | ||
def coflatMap[B](f: Cofree[S, A] => B)(implicit S: Functor[S]): Cofree[S, B] = | ||
Cofree[S, B](f(this), tailEval.map(S.map(_)(_.coflatMap(f)))) | ||
|
||
/** Replace each node in the computation with the subtree from that node downwards */ | ||
def coflatten(implicit S: Functor[S]): Cofree[S, Cofree[S, A]] = | ||
Cofree[S, Cofree[S, A]](this, tailEval.map(S.map(_)(_.coflatten))) | ||
|
||
/** Alias for head. */ | ||
def extract: A = head | ||
|
||
/** Evaluate just the tail. */ | ||
def forceTail: Cofree[S, A] = | ||
Cofree[S, A](head, Eval.now(tailEval.value)) | ||
|
||
/** Evaluate the entire Cofree tree. */ | ||
def forceAll(implicit S: Functor[S]): Cofree[S, A] = | ||
Cofree[S, A](head, Eval.now(tailEval.map(S.map(_)(_.forceAll)).value)) | ||
|
||
} | ||
|
||
object Cofree extends CofreeInstances { | ||
|
||
/** Cofree anamorphism, lazily evaluated. */ | ||
def unfold[F[_], A](a: A)(f: A => F[A])(implicit F: Functor[F]): Cofree[F, A] = | ||
Cofree[F, A](a, Eval.later(F.map(f(a))(unfold(_)(f)))) | ||
|
||
} | ||
|
||
sealed abstract class CofreeInstances2 { | ||
/** low priority `Reducible` instance */ | ||
implicit def catsReducibleForCofree[F[_] : Foldable]: Reducible[Cofree[F, ?]] = | ||
new CofreeReducible[F] { | ||
def F = implicitly | ||
} | ||
} | ||
|
||
sealed abstract class CofreeInstances1 extends CofreeInstances2 { | ||
/** low priority `Traverse` instance */ | ||
implicit def catsTraverseForCofree[F[_] : Traverse]: Traverse[Cofree[F, ?]] = | ||
new CofreeTraverse[F] { | ||
def F = implicitly | ||
} | ||
} | ||
|
||
sealed abstract class CofreeInstances extends CofreeInstances1 { | ||
implicit def catsFreeComonadForCofree[S[_] : Functor]: Comonad[Cofree[S, ?]] = new CofreeComonad[S] { | ||
def F = implicitly | ||
} | ||
} | ||
|
||
private trait CofreeComonad[S[_]] extends Comonad[Cofree[S, ?]] { | ||
implicit def F: Functor[S] | ||
|
||
override final def extract[A](p: Cofree[S, A]): A = p.extract | ||
|
||
override final def coflatMap[A, B](a: Cofree[S, A])(f: Cofree[S, A] => B): Cofree[S, B] = a.coflatMap(f) | ||
|
||
override final def coflatten[A](a: Cofree[S, A]): Cofree[S, Cofree[S, A]] = a.coflatten | ||
|
||
override final def map[A, B](a: Cofree[S, A])(f: A => B): Cofree[S, B] = a.map(f) | ||
} | ||
|
||
private trait CofreeReducible[F[_]] extends Reducible[Cofree[F, ?]] { | ||
implicit def F: Foldable[F] | ||
|
||
override final def foldMap[A, B](fa: Cofree[F, A])(f: A => B)(implicit M: Monoid[B]): B = | ||
M.combine(f(fa.head), F.foldMap(fa.tailForced)(foldMap(_)(f))) | ||
|
||
override final def foldRight[A, B](fa: Cofree[F, A], z: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = | ||
f(fa.head, fa.tailEval.flatMap(F.foldRight(_, z)(foldRight(_, _)(f)))) | ||
|
||
override final def foldLeft[A, B](fa: Cofree[F, A], z: B)(f: (B, A) => B): B = | ||
F.foldLeft(fa.tailForced, f(z, fa.head))((b, cof) => foldLeft(cof, b)(f)) | ||
|
||
override final def reduceLeftTo[A, B](fa: Cofree[F, A])(z: A => B)(f: (B, A) => B): B = | ||
F.foldLeft(fa.tailForced, z(fa.head))((b, cof) => foldLeft(cof, b)(f)) | ||
|
||
override def reduceRightTo[A, B](fa: Cofree[F, A])(z: A => B)(f: (A, Eval[B]) => Eval[B]): Eval[B] = { | ||
foldRight(fa, Eval.now((None: Option[B]))) { | ||
case (l, e) => e.flatMap { | ||
case None => Eval.now(Some(z(l))) | ||
case Some(r) => f(l, Eval.now(r)).map(Some(_)) | ||
} | ||
}.map(_.getOrElse(sys.error("reduceRightTo"))) | ||
} | ||
|
||
} | ||
|
||
private trait CofreeTraverse[F[_]] extends Traverse[Cofree[F, ?]] with CofreeReducible[F] with CofreeComonad[F] { | ||
implicit def F: Traverse[F] | ||
|
||
override final def traverse[G[_], A, B](fa: Cofree[F, A])(f: A => G[B])(implicit G: Applicative[G]): G[Cofree[F, B]] = | ||
G.map2(f(fa.head), F.traverse(fa.tailForced)(traverse(_)(f)))((h, t) => Cofree[F, B](h, Eval.now(t))) | ||
|
||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package cats | ||
package free | ||
|
||
import cats.data.NonEmptyList | ||
import cats.tests.CatsSuite | ||
import cats.laws.discipline.{CartesianTests, ComonadTests, ReducibleTests, SerializableTests, TraverseTests} | ||
import cats.syntax.list._ | ||
import org.scalacheck.{Arbitrary, Cogen, Gen} | ||
|
||
class CofreeTests extends CatsSuite { | ||
|
||
import CofreeTests._ | ||
|
||
implicit val iso = CartesianTests.Isomorphisms.invariant[Cofree[Option, ?]] | ||
|
||
checkAll("Cofree[Option, ?]", ComonadTests[Cofree[Option, ?]].comonad[Int, Int, Int]) | ||
locally { | ||
implicit val instance = Cofree.catsTraverseForCofree[Option] | ||
checkAll("Cofree[Option, ?]", TraverseTests[Cofree[Option, ?]].traverse[Int, Int, Int, Int, Option, Option]) | ||
checkAll("Traverse[Cofree[Option, ?]]", SerializableTests.serializable(Traverse[Cofree[Option, ?]])) | ||
} | ||
locally { | ||
implicit val instance = Cofree.catsReducibleForCofree[Option] | ||
checkAll("Cofree[Option, ?]", ReducibleTests[Cofree[Option, ?]].reducible[Option, Int, Int]) | ||
checkAll("Reducible[Cofree[Option, ?]]", SerializableTests.serializable(Reducible[Cofree[Option, ?]])) | ||
} | ||
checkAll("Comonad[Cofree[Option, ?]]", SerializableTests.serializable(Comonad[Cofree[Option, ?]])) | ||
|
||
test("Cofree.unfold") { | ||
val unfoldedHundred: CofreeNel[Int] = Cofree.unfold[Option, Int](1)(i => if (i == 100) None else Some(i + 1)) | ||
val nelUnfoldedHundred: NonEmptyList[Int] = NonEmptyList.fromListUnsafe(List.tabulate(99)(identity)) | ||
cofNelToNel(unfoldedHundred) === nelUnfoldedHundred | ||
} | ||
|
||
} | ||
|
||
object CofreeTests extends CofreeTestsInstances | ||
|
||
sealed trait CofreeTestsInstances { | ||
|
||
type CofreeNel[A] = Cofree[Option, A] | ||
|
||
implicit def cofNelEq[A](implicit e: Eq[A]): Eq[CofreeNel[A]] = new Eq[CofreeNel[A]] { | ||
override def eqv(a: CofreeNel[A], b: CofreeNel[A]): Boolean = { | ||
def tr(a: CofreeNel[A], b: CofreeNel[A]): Boolean = | ||
(a.tailForced, b.tailForced) match { | ||
case (Some(at), Some(bt)) if e.eqv(a.head, b.head) => tr(at, bt) | ||
case (None, None) if e.eqv(a.head, b.head) => true | ||
case _ => false | ||
} | ||
tr(a, b) | ||
} | ||
} | ||
|
||
|
||
implicit def CofreeOptionCogen[A: Cogen]: Cogen[CofreeNel[A]] = | ||
implicitly[Cogen[List[A]]].contramap[CofreeNel[A]](cofNelToNel(_).toList) | ||
|
||
implicit def CofreeOptionArb[A: Arbitrary]: Arbitrary[CofreeNel[A]] = { | ||
val arb = Arbitrary { | ||
Gen.resize(20, Gen.nonEmptyListOf(implicitly[Arbitrary[A]].arbitrary)) | ||
} | ||
Arbitrary { | ||
arb.arbitrary.map(l => (l.head, l.tail) match { | ||
case (h, Nil) => nelToCofNel(NonEmptyList(h, Nil)) | ||
case (h, t) => nelToCofNel(NonEmptyList(h, t)) | ||
}) | ||
} | ||
} | ||
|
||
val nelToCofNel = new (NonEmptyList ~> CofreeNel) { | ||
override def apply[A](fa: NonEmptyList[A]): CofreeNel[A] = | ||
Cofree[Option, A](fa.head, Eval.later(fa.tail.toNel.map(apply))) | ||
} | ||
|
||
val cofNelToNel = new (CofreeNel ~> NonEmptyList) { | ||
override def apply[A](fa: CofreeNel[A]): NonEmptyList[A] = | ||
NonEmptyList[A](fa.head, fa.tailForced.fold[List[A]](Nil)(apply(_).toList)) | ||
} | ||
|
||
} |