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

Add Representable Functor #2284

Merged
merged 6 commits into from
Jul 9, 2018
Merged
Show file tree
Hide file tree
Changes from 2 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
90 changes: 90 additions & 0 deletions core/src/main/scala/cats/Representable.scala
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
}
}
23 changes: 23 additions & 0 deletions core/src/main/scala/cats/data/Kleisli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,29 @@ private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1

implicit def catsDataContravariantMonoidalForKleisli[F[_], A](implicit F0: ContravariantMonoidal[F]): ContravariantMonoidal[Kleisli[F, A, ?]] =
new KleisliContravariantMonoidal[F, A] { def F: ContravariantMonoidal[F] = F0 }

/**
* Witness for: Kleisli[M, E, A] <-> (E, R) => A
* if M is Representable
*/
implicit def catsDataRepresentableForKleisli[M[_], R, E](implicit
R: Representable.Aux[M, R],
FK: Functor[Kleisli[M, E, ?]]): Representable.Aux[Kleisli[M, E, ?], (E, R)] = new Representable[Kleisli[M, E, ?]] {

override type Representation = (E, R)

override val F: Functor[Kleisli[M, E, ?]] = FK

def index[A](f: Kleisli[M, E, A]): Representation => A = {
case (e, r) => R.index(f.run(e))(r)
}

def tabulate[A](f: Representation => A): Kleisli[M, E, A] = {
def curry[X, Y, Z](f: (X, Y) => Z): X => Y => Z = x => y => f(x, y)

Kleisli[M, E, A](curry(Function.untupled(f)) andThen R.tabulate)
}
}
}

private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 {
Expand Down
61 changes: 61 additions & 0 deletions core/src/main/scala/cats/data/RepresentableStore.scala
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)
Copy link
Contributor

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.

}
}

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)
}
}
7 changes: 7 additions & 0 deletions core/src/main/scala/cats/data/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,11 @@ package object data {

type RWS[E, L, S, A] = ReaderWriterState[E, L, S, A]
val RWS = ReaderWriterState

type Store[S, A] = RepresentableStore[S => ?, S, A]
object Store {
import cats.instances.function._
def apply[S, A](f: S => A, s: S): Store[S, A] =
RepresentableStore[S => ?, S, A](f, s)
}
}
1 change: 1 addition & 0 deletions core/src/main/scala/cats/implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ object implicits
with syntax.AllSyntaxBinCompat0
with syntax.AllSyntaxBinCompat1
with instances.AllInstances
with instances.AllInstancesBinCompat0
4 changes: 4 additions & 0 deletions core/src/main/scala/cats/instances/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@ trait AllInstances
with TupleInstances
with UUIDInstances
with VectorInstances

trait AllInstancesBinCompat0
extends FunctionInstancesBinCompat0
with Tuple2InstancesBinCompat0
12 changes: 12 additions & 0 deletions core/src/main/scala/cats/instances/function.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ import annotation.tailrec
trait FunctionInstances extends cats.kernel.instances.FunctionInstances
with Function0Instances with Function1Instances

trait FunctionInstancesBinCompat0 {
/**
* Witness for: E => A <-> E => A
*/
implicit def catsStdRepresentableForFunction1[E](implicit EF: Functor[E => ?]): Representable.Aux[E => ?, E] = new Representable[E => ?] {
override type Representation = E
override val F: Functor[E => ?] = EF
override def tabulate[A](f: E => A): E => A = f
override def index[A](f: E => A): E => A = f
}
}

private[instances] sealed trait Function0Instances extends Function0Instances0 {
implicit val catsStdBimonadForFunction0: Bimonad[Function0] =
new Bimonad[Function0] {
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/scala/cats/instances/package.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats

package object instances {
object all extends AllInstances
object all extends AllInstances with AllInstancesBinCompat0
object bigInt extends BigIntInstances
object bigDecimal extends BigDecimalInstances
object bitSet extends BitSetInstances
Expand All @@ -15,6 +15,7 @@ package object instances {
object equiv extends EquivInstances
object float extends FloatInstances
object function extends FunctionInstances
with FunctionInstancesBinCompat0
object future extends FutureInstances
object int extends IntInstances
object invariant extends InvariantMonoidalInstances
Expand All @@ -36,6 +37,7 @@ package object instances {
object string extends StringInstances
object try_ extends TryInstances
object tuple extends TupleInstances
with Tuple2InstancesBinCompat0
object unit extends UnitInstances
object uuid extends UUIDInstances
object vector extends VectorInstances
Expand Down
23 changes: 23 additions & 0 deletions core/src/main/scala/cats/instances/tuple.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,33 @@ package cats
package instances

import cats.kernel.{CommutativeMonoid, CommutativeSemigroup}

import scala.annotation.tailrec

trait TupleInstances extends Tuple2Instances with cats.kernel.instances.TupleInstances

trait Tuple2InstancesBinCompat0 {

/**
* Witness for: (A, A) <-> Boolean => A
*/
implicit def catsDataRepresentableForPair(implicit PF: Functor[λ[P => (P, P)]]): Representable.Aux[λ[P => (P, P)], Boolean] = new Representable[λ[P => (P, P)]] {
override type Representation = Boolean
override val F: Functor[λ[P => (P, P)]] = PF

override def tabulate[A](f: Boolean => A): (A, A) = (f(true), f(false))

override def index[A](pair: (A, A)): Boolean => A = {
case true => pair._1
case false => pair._2
}
}

implicit val catsDataFunctorForPair: Functor[λ[P => (P, P)]] = new Functor[λ[P => (P, P)]] {
override def map[A, B](fa: (A, A))(f: A => B): (B, B) = (f(fa._1), f(fa._2))
}
}

sealed trait Tuple2Instances extends Tuple2Instances1 {
implicit val catsStdBitraverseForTuple2: Bitraverse[Tuple2] =
new Bitraverse[Tuple2] {
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/scala/cats/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ package object cats {
override def isEmpty[A](fa: Id[A]): Boolean = false
}

/**
* Witness for: Id[A] <-> Unit => A
Copy link
Contributor

Choose a reason for hiding this comment

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

we could add something similar for Eval[A] potentially.

Copy link
Contributor

Choose a reason for hiding this comment

The 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
I added an issue #2320

*/
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]
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,4 @@ trait AllSyntaxBinCompat1
with ParallelFlatSyntax
with SetSyntax
with ValidatedExtensionSyntax
with RepresentableSyntax
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ package object syntax {
object partialOrder extends PartialOrderSyntax
object profunctor extends ProfunctorSyntax
object reducible extends ReducibleSyntax
object representable extends RepresentableSyntax
object semigroup extends SemigroupSyntax
object semigroupal extends SemigroupalSyntax
object semigroupk extends SemigroupKSyntax
Expand Down
18 changes: 18 additions & 0 deletions core/src/main/scala/cats/syntax/representable.scala
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](fa: F[A]) {
def index(implicit R: Representable.Aux[F, R]): R => A = R.index(fa)
}

final class TabulateOps[A, R](f: R => A) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not extend AnyVal here and above?

Copy link
Contributor

Choose a reason for hiding this comment

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

If there is a reason can we comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I first wrote it, I had the ops class taking implicit params and AnyVal doesn't allow multiple param lists. I missed adding it back when I changed that, will fix that up now.

def tabulate[F[_]](implicit R: Representable.Aux[F, R]): F[A] = R.tabulate(f)
}
24 changes: 24 additions & 0 deletions laws/src/main/scala/cats/laws/RepresentableLaws.scala
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 }
}
18 changes: 18 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/Arbitrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,24 @@ object arbitrary extends ArbitraryInstances0 {
F: Arbitrary[(E, SA) => F[(L, SB, A)]]): Arbitrary[IndexedReaderWriterStateT[F, E, L, SA, SB, A]] =
Arbitrary(F.arbitrary.map(IndexedReaderWriterStateT(_)))

implicit def catsLawsArbitraryForRepresentableStore[F[_], S, A](implicit
R: Representable.Aux[F, S],
ArbS: Arbitrary[S],
ArbFA: Arbitrary[F[A]]
): Arbitrary[RepresentableStore[F, S, A]] = {
Arbitrary {
for {
fa <- ArbFA.arbitrary
s <- ArbS.arbitrary
} yield {
RepresentableStore[F, S, A](fa, s)
}
}
}

implicit def catsLawsCogenForRepresentableStore[F[_]: Representable, S, A](implicit CA: Cogen[A]): Cogen[RepresentableStore[F, S, A]] = {
CA.contramap(_.extract)
}
}

private[discipline] sealed trait ArbitraryInstances0 {
Expand Down
Loading