-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Add Representable Functor #2284
Changes from 5 commits
b99e781
0ff7eb9
c2c55ea
7f1c62f
cff12a6
c31445e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package cats | ||
|
||
/** | ||
* Representable. | ||
* | ||
* Is a witness to the isomorphism forall A. F[A] <-> Representation => A | ||
* | ||
* Must obey the laws defined in cats.laws.RepresentableLaws | ||
* i.e. | ||
* tabulate andThen index = identity | ||
* index andThen tabulate = identity | ||
* | ||
* Inspired by the Haskell representable package | ||
* http://hackage.haskell.org/package/representable-functors-3.2.0.2/docs/Data-Functor-Representable.html | ||
*/ | ||
trait Representable[F[_]] extends Serializable { | ||
|
||
def F: Functor[F] | ||
|
||
type Representation | ||
|
||
/** | ||
* Create a function that "indexes" into the `F` structure using `Representation` | ||
*/ | ||
def index[A](f: F[A]): Representation => A | ||
|
||
/** | ||
* Reconstructs the `F` structure using the index function | ||
*/ | ||
def tabulate[A](f: Representation => A): F[A] | ||
} | ||
|
||
private trait RepresentableMonad[F[_], R] extends Monad[F] { | ||
|
||
def R: Representable.Aux[F, R] | ||
|
||
override def pure[A](x: A): F[A] = R.tabulate(_ => x) | ||
|
||
override def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] = | ||
R.tabulate(a => R.index(f(R.index(fa)(a)))(a)) | ||
|
||
override def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] = { | ||
R.tabulate { r: R => | ||
@annotation.tailrec | ||
def loop(a: A): B = | ||
R.index(f(a))(r) match { | ||
case Right(b) => b | ||
case Left(a) => loop(a) | ||
} | ||
|
||
loop(a) | ||
} | ||
} | ||
} | ||
|
||
private trait RepresentableBimonad[F[_], R] extends RepresentableMonad[F, R] with Bimonad[F] { | ||
|
||
def M: Monoid[R] | ||
|
||
override def coflatMap[A, B](w: F[A])(f: F[A] => B): F[B] = | ||
R.tabulate(m => f(R.tabulate(x => R.index(w)(M.combine(m, x))))) | ||
|
||
override def extract[A](fa: F[A]): A = | ||
R.index(fa)(M.empty) | ||
} | ||
|
||
object Representable { | ||
type Aux[F[_], R] = Representable[F] { type Representation = R } | ||
|
||
/** | ||
* Summon the `Representable` instance for `F` | ||
*/ | ||
def apply[F[_]](implicit ev: Representable[F]): Representable[F] = ev | ||
|
||
/** | ||
* Derives a `Monad` instance for any `Representable` functor | ||
*/ | ||
def monad[F[_]](implicit Rep: Representable[F]): Monad[F] = new RepresentableMonad[F, Rep.Representation] { | ||
override def R: Representable.Aux[F, Rep.Representation] = Rep | ||
} | ||
|
||
/** | ||
* Derives a `Bimonad` instance for any `Representable` functor whos representation | ||
* has a `Monoid` instance. | ||
*/ | ||
def bimonad[F[_], R](implicit Rep: Representable.Aux[F, R], Mon: Monoid[R]): Bimonad[F] = new RepresentableBimonad[F, R] { | ||
override def R: Representable.Aux[F, R] = Rep | ||
override def M: Monoid[R] = Mon | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package cats.data | ||
|
||
import cats.{Comonad, Functor, Representable} | ||
|
||
/** | ||
* A generalisation of the Store comonad, for any `Representable` functor. | ||
* `Store` is the dual of `State` | ||
*/ | ||
final case class RepresentableStore[F[_], S, A](fa: F[A], index: S)(implicit R: Representable.Aux[F, S]) { | ||
/** | ||
* Inspect the value at "index" s | ||
*/ | ||
def peek(s: S): A = R.index(fa)(s) | ||
|
||
/** | ||
* Extract the value at the current index. | ||
*/ | ||
lazy val extract: A = peek(index) | ||
|
||
/** | ||
* Duplicate the store structure | ||
*/ | ||
lazy val coflatten: RepresentableStore[F, S, RepresentableStore[F, S, A]] = | ||
RepresentableStore(R.tabulate(idx => RepresentableStore(fa, idx)), index) | ||
|
||
def map[B](f: A => B): RepresentableStore[F, S, B] = { | ||
RepresentableStore(R.F.map(fa)(f), index) | ||
} | ||
|
||
/** | ||
* Given a functorial computation on the index `S` peek at the value in that functor. | ||
* | ||
* {{{ | ||
* import cats._, implicits._, data.Store | ||
* | ||
* val initial = List("a", "b", "c") | ||
* val store = Store(idx => initial.get(idx).getOrElse(""), 0) | ||
* val adjacent = store.experiment[List] { idx => List(idx - 1, idx, idx + 1) } | ||
* | ||
* require(adjacent == List("", "a", "b")) | ||
* }}} | ||
*/ | ||
def experiment[G[_]](fn: S => G[S])(implicit G: Functor[G]): G[A] = { | ||
G.map(fn(index))(peek) | ||
} | ||
} | ||
|
||
object RepresentableStore { | ||
|
||
implicit def catsDataRepresentableStoreComonad[F[_], S](implicit R: Representable[F]): Comonad[RepresentableStore[F, S, ?]] = | ||
new Comonad[RepresentableStore[F, S, ?]] { | ||
override def extract[B](x: RepresentableStore[F, S, B]): B = | ||
x.extract | ||
|
||
override def coflatMap[A, B](fa: RepresentableStore[F, S, A])(f: RepresentableStore[F, S, A] => B): RepresentableStore[F, S, B] = | ||
fa.coflatten.map(f) | ||
|
||
override def map[A, B](fa: RepresentableStore[F, S, A])(f: A => B): RepresentableStore[F, S, B] = | ||
fa.map(f) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,6 +77,18 @@ package object cats { | |
override def isEmpty[A](fa: Id[A]): Boolean = false | ||
} | ||
|
||
/** | ||
* Witness for: Id[A] <-> Unit => A | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could add something similar for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 , Since this has been blocking 1.2.0 release, I propose we tackle that in a separate PR |
||
*/ | ||
implicit val catsRepresentableForId: Representable.Aux[Id, Unit] = new Representable[Id] { | ||
override type Representation = Unit | ||
override val F: Functor[Id] = Functor[Id] | ||
|
||
override def tabulate[A](f: Unit => A): Id[A] = f(()) | ||
|
||
override def index[A](f: Id[A]): Unit => A = (_: Unit) => f | ||
} | ||
|
||
implicit val catsParallelForId: Parallel[Id, Id] = Parallel.identity | ||
|
||
type Eq[A] = cats.kernel.Eq[A] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package cats | ||
package syntax | ||
|
||
trait RepresentableSyntax { | ||
implicit final def catsSyntaxTabulate[A, R](f: R => A): TabulateOps[A, R] = | ||
new TabulateOps[A, R](f) | ||
|
||
implicit final def catsSyntaxIndex[F[_], A, R](fa: F[A])(implicit R: Representable.Aux[F, R]): IndexOps[F, A, R] = | ||
new IndexOps[F, A, R](fa) | ||
} | ||
|
||
final class IndexOps[F[_], A, R](val fa: F[A]) extends AnyVal { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this have R on the class? Maybe it doesn’t matter but it seems like it should be on index. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my tests it didn't seem to make a difference, but I think you're right, it makes more sense to put it on index. I'll make that change now. |
||
def index(implicit R: Representable.Aux[F, R]): R => A = R.index(fa) | ||
} | ||
|
||
final class TabulateOps[A, R](val f: R => A) extends AnyVal { | ||
def tabulate[F[_]](implicit R: Representable.Aux[F, R]): F[A] = R.tabulate(f) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package cats | ||
package laws | ||
|
||
|
||
/** | ||
* Laws that must be obeyed by any `Representable` functor. | ||
*/ | ||
trait RepresentableLaws[F[_], R] { | ||
|
||
implicit val R: Representable.Aux[F, R] | ||
|
||
def indexTabulateIsId[B](fb: F[B]): IsEq[F[B]] = { | ||
R.tabulate(R.index(fb)) <-> fb | ||
} | ||
|
||
def tabulateIndexIsId[B](f: R => B, x: R): IsEq[B] = { | ||
R.index(R.tabulate(f))(x) <-> f(x) | ||
} | ||
} | ||
|
||
object RepresentableLaws { | ||
def apply[F[_], R](implicit ev: Representable.Aux[F, R]): RepresentableLaws[F, R] = | ||
new RepresentableLaws[F, R] { val R: Representable.Aux[F, R] = ev } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mind to add a test for this method? I think a doctest would probably suffice.