-
-
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.
Merge pull request #3836 from TimWSpence/store-commonad
- Loading branch information
Showing
6 changed files
with
267 additions
and
4 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
137 changes: 137 additions & 0 deletions
137
core/src/main/scala/cats/data/RepresentableStoreT.scala
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,137 @@ | ||
package cats.data | ||
|
||
import cats.{Applicative, Comonad, Functor, Monoid, Representable} | ||
|
||
/* | ||
* The dual of `StateT`. Stores some state `A` indexed by | ||
* a type `S` with the notion of a cursor tracking the | ||
* current position in the index. | ||
* | ||
* This state can be extracted if the underlying `F` has | ||
* a Comonad instance. | ||
* | ||
* This is the (co)monad-transformer version of `RepresentableStore` | ||
*/ | ||
final case class RepresentableStoreT[W[_], F[_], S, A](runF: W[F[A]], index: S)(implicit F: Representable.Aux[F, S]) { | ||
|
||
def run(implicit W: Functor[W]): W[A] = W.map(runF)(fa => F.index(fa)(index)) | ||
|
||
/** | ||
* Peek at what the focus would be for a given focus s. | ||
*/ | ||
def peek(s: S)(implicit W: Comonad[W]): A = W.extract(W.map(runF)(fa => F.index(fa)(s))) | ||
|
||
/** | ||
* Peek at what the focus would be if the current focus where transformed | ||
* with the given function. | ||
*/ | ||
def peeks(f: S => S)(implicit W: Comonad[W]): A = peek(f(index)) | ||
|
||
/** | ||
* Set the current focus. | ||
*/ | ||
def seek(s: S): RepresentableStoreT[W, F, S, A] = RepresentableStoreT(runF, s) | ||
|
||
/** | ||
* Modify the current focus with the given function. | ||
*/ | ||
def seeks(f: S => S): RepresentableStoreT[W, F, S, A] = seek(f(index)) | ||
|
||
/** | ||
* Extract the focus at the current index. | ||
*/ | ||
def extract(implicit W: Comonad[W]): A = peek(index) | ||
|
||
/** | ||
* `coflatMap` is the dual of `flatMap` on `FlatMap`. It applies | ||
* a value in a context to a function that takes a value | ||
* in a context and returns a normal value. | ||
*/ | ||
def coflatMap[B](f: RepresentableStoreT[W, F, S, A] => B)(implicit W: Comonad[W]): RepresentableStoreT[W, F, S, B] = | ||
RepresentableStoreT( | ||
W.map(W.coflatten(runF))((x: W[F[A]]) => F.tabulate(s => f(RepresentableStoreT(x, s)))), | ||
index | ||
) | ||
|
||
/** | ||
* `coflatten` is the dual of `flatten` on `FlatMap`. Whereas flatten removes | ||
* a layer of `F`, coflatten adds a layer of `F` | ||
*/ | ||
def coflatten(implicit W: Comonad[W]): RepresentableStoreT[W, F, S, RepresentableStoreT[W, F, S, A]] = | ||
RepresentableStoreT( | ||
W.map(W.coflatten(runF))((x: W[F[A]]) => F.tabulate(s => RepresentableStoreT(x, s))), | ||
index | ||
) | ||
|
||
/** | ||
* Functor `map` for StoreT | ||
*/ | ||
def map[B](f: A => B)(implicit W: Functor[W]): RepresentableStoreT[W, F, S, B] = RepresentableStoreT( | ||
W.map(runF)((fa: F[A]) => F.F.map(fa)(f)), | ||
index | ||
) | ||
|
||
/** | ||
* Given a functorial computation on the index `S` peek at the value in that functor. | ||
*/ | ||
def experiment[G[_]](f: S => G[S])(implicit W: Comonad[W], G: Functor[G]): G[A] = | ||
G.map(f(index))(peek(_)) | ||
|
||
} | ||
|
||
object RepresentableStoreT extends RepresentableStoreTInstances1 { | ||
|
||
def pure[W[_], F[_], S, A]( | ||
x: A | ||
)(implicit W: Applicative[W], F: Representable.Aux[F, S], S: Monoid[S]): RepresentableStoreT[W, F, S, A] = | ||
RepresentableStoreT(W.pure(F.tabulate((_: S) => x)), S.empty) | ||
|
||
implicit def comonadForStoreT[W[_]: Comonad, F[_], S]: Comonad[RepresentableStoreT[W, F, S, *]] = | ||
new Comonad[RepresentableStoreT[W, F, S, *]] { | ||
|
||
override def map[A, B](fa: RepresentableStoreT[W, F, S, A])(f: A => B): RepresentableStoreT[W, F, S, B] = | ||
fa.map(f) | ||
|
||
override def coflatMap[A, B](fa: RepresentableStoreT[W, F, S, A])( | ||
f: RepresentableStoreT[W, F, S, A] => B | ||
): RepresentableStoreT[W, F, S, B] = | ||
fa.coflatMap(f) | ||
|
||
override def extract[A](fa: RepresentableStoreT[W, F, S, A]): A = fa.extract | ||
|
||
} | ||
} | ||
|
||
trait RepresentableStoreTInstances1 extends RepresentableStoreTInstances2 { | ||
|
||
implicit def applicativeForStoreT[W[_], F[_], S](implicit | ||
W: Applicative[W], | ||
F: Representable.Aux[F, S], | ||
S: Monoid[S] | ||
): Applicative[RepresentableStoreT[W, F, S, *]] = | ||
new Applicative[RepresentableStoreT[W, F, S, *]] { | ||
|
||
def pure[A](x: A): RepresentableStoreT[W, F, S, A] = RepresentableStoreT.pure[W, F, S, A](x) | ||
|
||
def ap[A, B]( | ||
ff: RepresentableStoreT[W, F, S, A => B] | ||
)(fa: RepresentableStoreT[W, F, S, A]): RepresentableStoreT[W, F, S, B] = RepresentableStoreT( | ||
W.map(W.tuple2(ff.runF, fa.runF)) { case (f, a) => | ||
F.tabulate((s: S) => F.index(f)(s).apply(F.index(a)(s))) | ||
}, | ||
S.combine(ff.index, fa.index) | ||
) | ||
|
||
} | ||
} | ||
|
||
trait RepresentableStoreTInstances2 { | ||
|
||
implicit def functorForStoreT[W[_]: Functor, F[_], S]: Functor[RepresentableStoreT[W, F, S, *]] = | ||
new Functor[RepresentableStoreT[W, F, S, *]] { | ||
|
||
override def map[A, B](fa: RepresentableStoreT[W, F, S, A])(f: A => B): RepresentableStoreT[W, F, S, B] = | ||
fa.map(f) | ||
|
||
} | ||
} |
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
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
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
68 changes: 68 additions & 0 deletions
68
tests/src/test/scala/cats/tests/RepresentableStoreTSuite.scala
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,68 @@ | ||
package cats.tests | ||
|
||
import cats._ | ||
import cats.data.{StoreT, Validated} | ||
import cats.laws.discipline._ | ||
import cats.laws.discipline.arbitrary._ | ||
import cats.laws.discipline.eq._ | ||
import cats.syntax.eq._ | ||
import org.scalacheck.Prop._ | ||
import cats.data.RepresentableStoreT | ||
import org.scalacheck.{Arbitrary, Cogen} | ||
|
||
class RepresentableStoreTSuite extends CatsSuite { | ||
|
||
implicit val monoid: Monoid[MiniInt] = MiniInt.miniIntAddition | ||
|
||
implicit val scala2_12_makes_me_sad: Comonad[StoreT[Id, MiniInt, *]] = | ||
RepresentableStoreT.comonadForStoreT[Id, Function1[MiniInt, *], MiniInt] | ||
//Like, really, really, really sad | ||
val a: Arbitrary[Int] = implicitly[Arbitrary[Int]] | ||
val b: Eq[Int] = Eq[Int] | ||
val c: Arbitrary[StoreT[Id, MiniInt, Int]] = implicitly[Arbitrary[StoreT[Id, MiniInt, Int]]] | ||
val d: Cogen[Int] = implicitly[Cogen[Int]] | ||
val e: Cogen[StoreT[Id, MiniInt, Int]] = implicitly[Cogen[StoreT[Id, MiniInt, Int]]] | ||
val f: Eq[StoreT[Id, MiniInt, Int]] = Eq[StoreT[Id, MiniInt, Int]] | ||
val g: Eq[StoreT[Id, MiniInt, StoreT[Id, MiniInt, Int]]] = Eq[StoreT[Id, MiniInt, StoreT[Id, MiniInt, Int]]] | ||
val h: Eq[StoreT[Id, MiniInt, StoreT[Id, MiniInt, StoreT[Id, MiniInt, Int]]]] = | ||
Eq[StoreT[Id, MiniInt, StoreT[Id, MiniInt, StoreT[Id, MiniInt, Int]]]] | ||
|
||
checkAll("StoreT[Id, MiniInt, *]", | ||
ComonadTests[StoreT[Id, MiniInt, *]].comonad[Int, Int, Int]( | ||
a, | ||
b, | ||
a, | ||
b, | ||
a, | ||
b, | ||
c, | ||
d, | ||
d, | ||
d, | ||
e, | ||
e, | ||
f, | ||
g, | ||
h, | ||
f, | ||
f | ||
) | ||
) | ||
|
||
checkAll("Comonad[StoreT[Id, MiniInt, *]]", SerializableTests.serializable(Comonad[StoreT[Id, MiniInt, *]])) | ||
|
||
checkAll("StoreT[Validated[String, *], MiniInt, *]]", | ||
ApplicativeTests[StoreT[Validated[String, *], MiniInt, *]].applicative[MiniInt, MiniInt, MiniInt] | ||
) | ||
|
||
checkAll("Comonad[StoreT[Validated[String, *], MiniInt, *]]", | ||
SerializableTests.serializable(Applicative[StoreT[Validated[String, *], MiniInt, *]]) | ||
) | ||
|
||
test("extract and peek are consistent") { | ||
forAll { (store: StoreT[Id, String, String]) => | ||
assert(store.extract === (store.peek(store.index))) | ||
} | ||
} | ||
|
||
} |