Skip to content

Commit

Permalink
Merge pull request #628 from ceedubs/writert-instances
Browse files Browse the repository at this point in the history
Add WriterT instances for supertypes of Monad
  • Loading branch information
stew committed Nov 15, 2015
2 parents cbe820f + 1741ce5 commit 3ad2fd6
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 16 deletions.
103 changes: 95 additions & 8 deletions core/src/main/scala/cats/data/WriterT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) {
def value(implicit functorF: Functor[F]): F[V] =
functorF.map(run)(_._2)

def ap[Z](f: WriterT[F, L, V => Z])(implicit F: Apply[F], L: Semigroup[L]): WriterT[F, L, Z] =
WriterT(
F.map2(f.run, run){
case ((l1, fvz), (l2, v)) => (L.combine(l1, l2), fvz(v))
})

def map[Z](fn: V => Z)(implicit functorF: Functor[F]): WriterT[F, L, Z] =
WriterT {
functorF.map(run) { z => (z._1, fn(z._2)) }
Expand Down Expand Up @@ -36,21 +42,102 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) {
}
object WriterT extends WriterTInstances with WriterTFunctions

private[data] sealed abstract class WriterTInstances {
implicit def writerTMonad[F[_], L](implicit monadF: Monad[F], monoidL: Monoid[L]): Monad[WriterT[F, L, ?]] = {
new Monad[WriterT[F, L, ?]] {
override def pure[A](a: A): WriterT[F, L, A] =
WriterT.value[F, L, A](a)
private[data] sealed abstract class WriterTInstances extends WriterTInstances0 {

implicit def writerTIdMonad[L:Monoid]: Monad[WriterT[Id, L, ?]] =
writerTMonad[Id, L]

override def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] =
fa.flatMap(a => f(a))
// The Eq[(L, V)] can be derived from an Eq[L] and Eq[V], but we are waiting
// on an algebra release that includes https:/non/algebra/pull/82
implicit def writerTIdEq[L, V](implicit E: Eq[(L, V)]): Eq[WriterT[Id, L, V]] =
writerTEq[Id, L, V]
}

private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 {
implicit def writerTMonad[F[_], L](implicit F: Monad[F], L: Monoid[L]): Monad[WriterT[F, L, ?]] =
new WriterTMonad[F, L] {
implicit val F0: Monad[F] = F
implicit val L0: Monoid[L] = L
}
}

implicit def writerTIdFunctor[L]: Functor[WriterT[Id, L, ?]] =
writerTFunctor[Id, L]

implicit def writerTIdFlatMap[L:Semigroup]: FlatMap[WriterT[Id, L, ?]] =
writerTFlatMap[Id, L]

implicit def writerTEq[F[_], L, V](implicit F: Eq[F[(L, V)]]): Eq[WriterT[F, L, V]] =
F.on(_.run)
}

private[data] sealed abstract class WriterTInstances1 extends WriterTInstances2 {
implicit def writerTApplicative[F[_], L](implicit F: Applicative[F], L: Monoid[L]): Applicative[WriterT[F, L, ?]] =
new WriterTApplicative[F, L] {
implicit val F0: Applicative[F] = F
implicit val L0: Monoid[L] = L
}
}
private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 {
implicit def writerTFlatMap[F[_], L](implicit F: FlatMap[F], L: Semigroup[L]): FlatMap[WriterT[F, L, ?]] =
new WriterTFlatMap[F, L] {
implicit val F0: FlatMap[F] = F
implicit val L0: Semigroup[L] = L
}
}

private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 {
implicit def writerTApply[F[_], L](implicit F: Apply[F], L: Semigroup[L]): Apply[WriterT[F, L, ?]] =
new WriterTApply[F, L] {
implicit val F0: Apply[F] = F
implicit val L0: Semigroup[L] = L
}
}

private[data] sealed abstract class WriterTInstances4 {
implicit def writerTFunctor[F[_], L](implicit F: Functor[F]): Functor[WriterT[F, L, ?]] = new WriterTFunctor[F, L] {
implicit val F0: Functor[F] = F
}
}

private[data] sealed trait WriterTFunctor[F[_], L] extends Functor[WriterT[F, L, ?]] {
implicit def F0: Functor[F]

def map[A, B](fa: WriterT[F, L, A])(f: A => B): WriterT[F, L, B] =
fa.map(f)
}

private[data] sealed trait WriterTApply[F[_], L] extends WriterTFunctor[F, L] with Apply[WriterT[F, L, ?]] {
override implicit def F0: Apply[F]
implicit def L0: Semigroup[L]

def ap[A, B](fa: WriterT[F, L, A])(f: WriterT[F, L, A => B]): WriterT[F, L, B] =
fa ap f
}

private[data] sealed trait WriterTFlatMap[F[_], L] extends WriterTApply[F, L] with FlatMap[WriterT[F, L, ?]] {
override implicit def F0: FlatMap[F]
implicit def L0: Semigroup[L]

def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] =
fa flatMap f
}

private[data] sealed trait WriterTApplicative[F[_], L] extends WriterTApply[F, L] with Applicative[WriterT[F, L, ?]] {
override implicit def F0: Applicative[F]
override implicit def L0: Monoid[L]

def pure[A](a: A): WriterT[F, L, A] =
WriterT.value[F, L, A](a)
}

private[data] sealed trait WriterTMonad[F[_], L] extends WriterTApplicative[F, L] with Monad[WriterT[F, L, ?]] {
override implicit def F0: Monad[F]
override implicit def L0: Monoid[L]

def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] =
fa.flatMap(f)
}

trait WriterTFunctions {
def putT[F[_], L, V](vf: F[V])(l: L)(implicit functorF: Functor[F]): WriterT[F, L, V] =
WriterT(functorF.map(vf)(v => (l, v)))
Expand Down
11 changes: 8 additions & 3 deletions laws/src/main/scala/cats/laws/discipline/Arbitrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.scalacheck.Arbitrary.{arbitrary => getArbitrary}
/**
* Arbitrary instances for cats.data
*/
object arbitrary {
object arbitrary extends ArbitraryInstances0 {

implicit def constArbitrary[A, B](implicit A: Arbitrary[A]): Arbitrary[Const[A, B]] =
Arbitrary(A.arbitrary.map(Const[A, B]))
Expand Down Expand Up @@ -76,10 +76,15 @@ object arbitrary {
as <- Gen.listOf(A.arbitrary).map(_.take(8))
} yield StreamingT.fromList(as))

implicit def writerTArbitrary[F[_], L, V](implicit F: Arbitrary[F[(L, V)]]): Arbitrary[WriterT[F, L, V]] =
Arbitrary(F.arbitrary.map(WriterT(_)))
implicit def writerArbitrary[L:Arbitrary, V:Arbitrary]: Arbitrary[Writer[L, V]] =
writerTArbitrary[Id, L, V]

// until this is provided by scalacheck
implicit def partialFunctionArbitrary[A, B](implicit F: Arbitrary[A => Option[B]]): Arbitrary[PartialFunction[A, B]] =
Arbitrary(F.arbitrary.map(Function.unlift))
}

private[discipline] sealed trait ArbitraryInstances0 {
implicit def writerTArbitrary[F[_], L, V](implicit F: Arbitrary[F[(L, V)]]): Arbitrary[WriterT[F, L, V]] =
Arbitrary(F.arbitrary.map(WriterT(_)))
}
137 changes: 132 additions & 5 deletions tests/src/test/scala/cats/tests/WriterTTests.scala
Original file line number Diff line number Diff line change
@@ -1,33 +1,160 @@
package cats
package tests

import cats.data.WriterT
import cats.data.{Writer, WriterT}
import cats.laws.discipline._
import cats.laws.discipline.eq._
import cats.laws.discipline.arbitrary._

import algebra.laws.OrderLaws
import org.scalacheck.Prop.forAll

class WriterTTests extends CatsSuite {
checkAll("WriterT[List, String, Int]", MonadTests[WriterT[List, String, ?]].monad[String, Int, Int])
type Logged[A] = Writer[ListWrapper[Int], A]

// we have a lot of generated lists of lists in these tests. We have to tell
// Scalacheck to calm down a bit so we don't hit memory and test duration
// issues.
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
PropertyCheckConfig(maxSize = 5, minSuccessful = 20)

checkAll("WriterT[List, Int, Int]", OrderLaws[WriterT[List, Int, Int]].eqv)
checkAll("Eq[WriterT[List, Int, Int]]", SerializableTests.serializable(Eq[WriterT[List, Int, Int]]))
// check that this resolves
Eq[Writer[Int, Int]]

test("double swap is a noop"){
forAll { w: WriterT[List, String, Int] =>
forAll { w: WriterT[List, Int, Int] =>
w.swap.swap should === (w)
}
}

test("reset on pure is a noop"){
forAll { i: Int =>
val w = Monad[WriterT[List, String, ?]].pure(i)
val w = Monad[WriterT[List, Int, ?]].pure(i)
w should === (w.reset)
}
}

test("reset consistencey"){
forAll { (i: Int, w1: WriterT[Id, String, Int], w2: WriterT[Id, String, Int]) =>
forAll { (i: Int, w1: WriterT[Id, Int, Int], w2: WriterT[Id, Int, Int]) =>
// if the value is the same, everything should be the same
w1.map(_ => i).reset should === (w2.map(_ => i).reset)
}
}

{
// F has a Functor and L has no Semigroup
implicit val F: Functor[ListWrapper] = ListWrapper.functor

checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", FunctorTests[WriterT[ListWrapper, ListWrapper[Int], ?]].functor[Int, Int, Int])
checkAll("Functor[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Functor[WriterT[ListWrapper, ListWrapper[Int], ?]]))

// just making sure this resolves; it's tested above
Functor[WriterT[Id, ListWrapper[Int], ?]]

Functor[Writer[ListWrapper[Int], ?]]

Functor[Logged]
}

// We have varying instances available depending on `F` and `L`.
// We also battle some inference issues with `Id`.
// Below we go through some gymnastics in order to test both the implicit
// resolution and the laws of these various instances.
{
// F has an Apply and L has a Semigroup
implicit val F: Apply[ListWrapper] = ListWrapper.monadCombine
implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroupK.algebra[Int]

Functor[WriterT[ListWrapper, ListWrapper[Int], ?]]
checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", ApplyTests[WriterT[ListWrapper, ListWrapper[Int], ?]].apply[Int, Int, Int])
checkAll("Apply[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Apply[WriterT[ListWrapper, ListWrapper[Int], ?]]))

Functor[WriterT[Id, ListWrapper[Int], ?]]
Apply[WriterT[Id, ListWrapper[Int], ?]]

Functor[Writer[ListWrapper[Int], ?]]
Apply[Writer[ListWrapper[Int], ?]]

Functor[Logged]
Apply[Logged]
}

{
// F has a FlatMap and L has a Semigroup
implicit val F: FlatMap[ListWrapper] = ListWrapper.monadCombine
implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroupK.algebra[Int]

Functor[WriterT[ListWrapper, ListWrapper[Int], ?]]
Apply[WriterT[ListWrapper, ListWrapper[Int], ?]]
checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", FlatMapTests[WriterT[ListWrapper, ListWrapper[Int], ?]].flatMap[Int, Int, Int])
checkAll("FlatMap[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(FlatMap[WriterT[ListWrapper, ListWrapper[Int], ?]]))

Functor[WriterT[Id, ListWrapper[Int], ?]]
Apply[WriterT[Id, ListWrapper[Int], ?]]
FlatMap[WriterT[Id, ListWrapper[Int], ?]]

Functor[Writer[ListWrapper[Int], ?]]
Apply[Writer[ListWrapper[Int], ?]]
FlatMap[Writer[ListWrapper[Int], ?]]

Functor[Logged]
Apply[Logged]
FlatMap[Logged]
}

{
// F has an Applicative and L has a Monoid
implicit val F: Applicative[ListWrapper] = ListWrapper.monadCombine
implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monadCombine.algebra[Int]

Functor[WriterT[ListWrapper, ListWrapper[Int], ?]]
Apply[WriterT[ListWrapper, ListWrapper[Int], ?]]
checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", ApplicativeTests[WriterT[ListWrapper, ListWrapper[Int], ?]].applicative[Int, Int, Int])
checkAll("Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]]))

Functor[WriterT[Id, ListWrapper[Int], ?]]
Apply[WriterT[Id, ListWrapper[Int], ?]]
Applicative[WriterT[Id, ListWrapper[Int], ?]]

Functor[Writer[ListWrapper[Int], ?]]
Apply[Writer[ListWrapper[Int], ?]]
Applicative[Writer[ListWrapper[Int], ?]]

Functor[Logged]
Apply[Logged]
Applicative[Logged]
}

{
// F has a Monad and L has a Monoid
implicit val F: Monad[ListWrapper] = ListWrapper.monadCombine
implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monadCombine.algebra[Int]

Functor[WriterT[ListWrapper, ListWrapper[Int], ?]]
Apply[WriterT[ListWrapper, ListWrapper[Int], ?]]
Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]]
FlatMap[WriterT[ListWrapper, ListWrapper[Int], ?]]
checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", MonadTests[WriterT[ListWrapper, ListWrapper[Int], ?]].monad[Int, Int, Int])
checkAll("Monad[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Monad[WriterT[ListWrapper, ListWrapper[Int], ?]]))

Functor[WriterT[Id, ListWrapper[Int], ?]]
Apply[WriterT[Id, ListWrapper[Int], ?]]
Applicative[WriterT[Id, ListWrapper[Int], ?]]
FlatMap[WriterT[Id, ListWrapper[Int], ?]]
Monad[WriterT[Id, ListWrapper[Int], ?]]

Functor[Writer[ListWrapper[Int], ?]]
Apply[Writer[ListWrapper[Int], ?]]
Applicative[Writer[ListWrapper[Int], ?]]
FlatMap[Writer[ListWrapper[Int], ?]]
Monad[Writer[ListWrapper[Int], ?]]

Functor[Logged]
Apply[Logged]
Applicative[Logged]
FlatMap[Logged]
Monad[Logged]
}
}

0 comments on commit 3ad2fd6

Please sign in to comment.