Skip to content

Commit

Permalink
Close #58 - Add more WriterT instances
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin-lee committed Sep 19, 2019
1 parent 0b59054 commit 5be6e80
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 38 deletions.
93 changes: 68 additions & 25 deletions src/main/scala/just/fp/WriterT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@ final case class WriterT[F[_], W, A](run: F[(W, A)]) {
def map[B](f: A => B)(implicit F: Functor[F]): WriterT[F, W, B] =
writerT(F.map(run)(wa => (wa._1, f(wa._2))))

def ap[B](fa: => WriterT[F, W, A => B])(implicit M: Monad[F], S: SemiGroup[W]): WriterT[F, W, B] =
writerT(
M.flatMap(run){ wa =>
M.map(fa.run) { wfa =>
(S.append(wa._1, wfa._1), wfa._2(wa._2))
}
}
)
def ap[B](fa: => WriterT[F, W, A => B])(implicit F: Applicative[F], S: SemiGroup[W]): WriterT[F, W, B] =
WriterT {
F.ap(run)(F.map(fa.run) { case (w2, a2) =>
wa1 => (S.append(wa1._1, w2), a2(wa1._2))
})
}

def flatMap[B](f: A => WriterT[F, W, B])(implicit M: Monad[F], S: SemiGroup[W]): WriterT[F, W, B] =
writerT(M.flatMap(run) { wa =>
Expand All @@ -36,33 +34,78 @@ final case class WriterT[F[_], W, A](run: F[(W, A)]) {
F.map(run)(_._2)
}

object WriterT extends WriterTMonadInstance {
sealed trait WriterTMonadInstances extends WriterMonadInstance

object WriterT extends WriterTMonadInstances {
def writerT[F[_], W, A](f: F[(W, A)]): WriterT[F, W, A] = WriterT(f)
}

sealed abstract class WriterTMonadInstance extends WriterInstance {
private trait WriterTFunctor[F[_], W] extends Functor[WriterT[F, W, ?]] {
implicit def F: Functor[F]

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

implicit def writerTMonad[F[_], W](implicit F0: Monad[F], S0: Monoid[W]): Monad[WriterT[F, W, ?]] = new Monad[WriterT[F, W, ?]] {
implicit val F: Monad[F] = F0
implicit val S: Monoid[W] = S0
private trait WriterTApplicative[F[_], W] extends Applicative[WriterT[F, W, ?]] with WriterTFunctor[F, W] {
override implicit def F: Applicative[F]
implicit def W: Monoid[W]

def flatMap[A, B](fa: WriterT[F, W, A])(f: A => WriterT[F, W, B]): WriterT[F, W, B] =
fa.flatMap(f)(F, S)
override def pure[A](a: => A): WriterT[F, W, A] = WriterT.writerT(F.pure((W.zero, a)))

def pure[A](a: => A): WriterT[F, W, A] = WriterT(F.pure((S.zero, a)))
}
override def ap[A, B](fa: => WriterT[F, W, A])(fab: => WriterT[F, W, A => B]): WriterT[F, W, B] =
fa.ap(fab)(F, W)
}

sealed abstract class WriterInstance {
private trait WriterTMonad[F[_], W] extends Monad[WriterT[F, W, ?]] with WriterTApplicative[F, W] {
override implicit def F: Monad[F]

implicit def writerMonad[W](implicit S0: Monoid[W]): Monad[WriterT[Id, W, ?]] = new Monad[WriterT[Id, W, ?]] {
implicit val F: Monad[Id] = idInstance
implicit val S: Monoid[W] = S0
override def flatMap[A, B](ma: WriterT[F, W, A])(f: A => WriterT[F, W, B]): WriterT[F, W, B] =
ma.flatMap(f)(F, W)
}

def flatMap[A, B](fa: WriterT[Id, W, A])(f: A => WriterT[Id, W, B]): WriterT[Id, W, B] =
fa.flatMap(f)(F, S)
sealed abstract class WriterTFunctorInstance {

def pure[A](a: => A): WriterT[Id, W, A] = WriterT(F.pure((S.zero, a)))
}
implicit def writerTMonad[F[_], W](implicit F0: Functor[F]): Functor[WriterT[F, W, ?]] =
new WriterTFunctor[F, W] {
implicit val F: Functor[F] = F0
}
}
sealed abstract class WriterTApplicativeInstance extends WriterTFunctorInstance {

implicit def writerTMonad[F[_], W](implicit F0: Applicative[F], S0: Monoid[W]): Applicative[WriterT[F, W, ?]] =
new WriterTApplicative[F, W] {
override implicit val F: Applicative[F] = F0
implicit val W: Monoid[W] = S0
}
}

sealed abstract class WriterTMonadInstance extends WriterTApplicativeInstance {

implicit def writerTMonad[F[_], W](implicit F0: Monad[F], S0: Monoid[W]): Monad[WriterT[F, W, ?]] =
new WriterTMonad[F, W] {
override implicit val F: Monad[F] = F0
override implicit val W: Monoid[W] = S0
}

implicit def writerTEqual[F[_], W, A](implicit EQ: Equal[F[(W, A)]]): Equal[WriterT[F, W, A]] =
new Equal[WriterT[F, W, A]] {
override def equal(x: WriterT[F, W, A], y: WriterT[F, W, A]): Boolean =
EQ.equal(x.run, y.run)
}

}

sealed abstract class WriterMonadInstance extends WriterTMonadInstance {

implicit def writerMonad[W](implicit S0: Monoid[W]): Monad[Writer[W, ?]] =
new WriterTMonad[Id, W] {
override implicit val F: Functor[Id] with Applicative[Id] with Monad[Id] = idInstance
override implicit val W: Monoid[W] = S0
}

implicit def writerEqual[W, A](implicit EQ: Equal[(W, A)]): Equal[Writer[W, A]] =
new Equal[Writer[W, A]] {
override def equal(x: Writer[W, A], y: Writer[W, A]): Boolean =
EQ.equal(x.run, y.run)
}
}
2 changes: 1 addition & 1 deletion src/test/scala/just/fp/ApplicativeSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ object ApplicativeSpec extends Properties {
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

import Specs.FutureEqualInstance.futureEqual
import just.fp.testing.EqualUtil.FutureEqualInstance.futureEqual

def genFuture: Gen[Future[Int]] = Gens.genFuture(Gens.genIntFromMinToMax)

Expand Down
2 changes: 1 addition & 1 deletion src/test/scala/just/fp/FunctorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ object FunctorSpec extends Properties {
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

import Specs.FutureEqualInstance.futureEqual
import just.fp.testing.EqualUtil.FutureEqualInstance.futureEqual

def genFuture: Gen[Future[Int]] = Gens.genFuture(Gens.genIntFromMinToMax)

Expand Down
6 changes: 6 additions & 0 deletions src/test/scala/just/fp/Gens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,10 @@ object Gens {
def genEither[A, B](genA: Gen[A], genB: Gen[B]): Gen[Either[A, B]] =
Gen.choice1(genA.map(Left(_)), genB.map(Right(_)))

def genWriterT[F[_], W, A](genW: Gen[W], genA: Gen[A])(implicit F: Monad[F]): Gen[WriterT[F, W, A]] =
for {
w <- genW
a <- genA
} yield WriterT(F.pure((w, a)))

}
2 changes: 1 addition & 1 deletion src/test/scala/just/fp/MonadSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ object MonadSpec extends Properties {
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

import Specs.FutureEqualInstance.futureEqual
import just.fp.testing.EqualUtil.FutureEqualInstance.futureEqual

def genFuture: Gen[Future[Int]] = Gens.genFuture(Gens.genIntFromMinToMax)

Expand Down
10 changes: 0 additions & 10 deletions src/test/scala/just/fp/Specs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,4 @@ object Specs {
}
}

object FutureEqualInstance {
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}

implicit def futureEqual[A](implicit EQ: Equal[A]): Equal[Future[A]] = new Equal[Future[A]] {
override def equal(x: Future[A], y: Future[A]): Boolean =
Await.result(x.flatMap(a => y.map(b => EQ.equal(a, b))), 1.second)
}
}
}
101 changes: 101 additions & 0 deletions src/test/scala/just/fp/WriterTSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package just.fp

import hedgehog._
import hedgehog.runner._

/**
* @author Kevin Lee
* @since 2019-09-19
*/
object WriterTSpec extends Properties {

override def tests: List[Test] = List(
property("testWriterTFunctorLaws", WriterTFunctorLaws.laws)
, property("testWriterTApplicativeLaws", WriterTApplicativeLaws.laws)
, property("testWriterTMonadLaws", WriterTMonadLaws.laws)
, property("testWriterFunctorLaws", WriterFunctorLaws.laws)
, property("testWriterApplicativeLaws", WriterApplicativeLaws.laws)
, property("testWriterMonadLaws", WriterMonadLaws.laws)
)

object WriterTFunctorLaws {
type WriterTOption[A] = WriterT[Option, String, A]

def genWriterT: Gen[WriterTOption[Int]] = Gens.genWriterT(Gens.genUnicodeString, Gens.genIntFromMinToMax)

def laws: Property =
Specs.functorLaws.laws[WriterTOption](
genWriterT
, Gens.genIntToInt
)
}

object WriterTApplicativeLaws {
type WriterTOption[A] = WriterT[Option, String, A]

def genWriterT: Gen[WriterTOption[Int]] = Gens.genWriterT(Gens.genUnicodeString, Gens.genIntFromMinToMax)

def laws: Property =
Specs.applicativeLaws.laws[WriterTOption](
genWriterT
, Gens.genIntFromMinToMax
, Gens.genIntToInt
)
}

object WriterTMonadLaws {
type WriterTOption[A] = WriterT[Option, String, A]

def genWriterT: Gen[WriterTOption[Int]] = Gens.genWriterT(Gens.genUnicodeString, Gens.genIntFromMinToMax)

def laws: Property =
Specs.monadLaws.laws[WriterTOption](
genWriterT
, Gens.genIntFromMinToMax
, Gens.genIntToInt
, Gens.genAToMonadA(Gens.genIntToInt)
)
}

import just.fp.testing.EqualUtil.TupleEqualInstance._

object WriterFunctorLaws {
type WriterString[A] = Writer[String, A]

def genWriter: Gen[WriterString[Int]] = Gens.genWriterT(Gens.genUnicodeString, Gens.genIntFromMinToMax)

def laws: Property =
Specs.functorLaws.laws[WriterString](
genWriter
, Gens.genIntToInt
)
}

object WriterApplicativeLaws {
type WriterString[A] = Writer[String, A]

def genWriter: Gen[WriterString[Int]] = Gens.genWriterT(Gens.genUnicodeString, Gens.genIntFromMinToMax)

def laws: Property =
Specs.applicativeLaws.laws[WriterString](
genWriter
, Gens.genIntFromMinToMax
, Gens.genIntToInt
)
}

object WriterMonadLaws {

type WriterString[A] = Writer[String, A]

def genWriter: Gen[WriterString[Int]] = Gens.genWriterT(Gens.genUnicodeString, Gens.genIntFromMinToMax)

def laws: Property =
Specs.monadLaws.laws[WriterString](
genWriter
, Gens.genIntFromMinToMax
, Gens.genIntToInt
, Gens.genAToMonadA(Gens.genIntToInt)
)
}
}
32 changes: 32 additions & 0 deletions src/test/scala/just/fp/testing/EqualUtil.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package just.fp.testing

import just.fp.Equal

/**
* @author Kevin Lee
* @since 2019-09-19
*/
object EqualUtil {

object FutureEqualInstance {
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}

implicit def futureEqual[A](implicit EQ: Equal[A]): Equal[Future[A]] = new Equal[Future[A]] {
override def equal(x: Future[A], y: Future[A]): Boolean =
Await.result(x.flatMap(a => y.map(b => EQ.equal(a, b))), 1.second)
}
}

object TupleEqualInstance {

import just.fp.JustSyntax._

implicit def tupleEq[A: Equal, B: Equal]: Equal[(A, B)] = new Equal[(A, B)] {
override def equal(x: (A, B), y: (A, B)): Boolean =
x._1 === y._1 && x._2 === y._2
}

}
}

0 comments on commit 5be6e80

Please sign in to comment.