Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
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://github.com/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]
}
}