Skip to content

Commit

Permalink
Merge pull request #65 from Kevin-Lee/task/59/more-eithert-instances
Browse files Browse the repository at this point in the history
Close #59 - Add more EitherT instances
  • Loading branch information
kevin-lee committed Sep 19, 2019
2 parents aa05f31 + 68bdeeb commit 5312d5c
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 7 deletions.
76 changes: 69 additions & 7 deletions src/main/scala/just/fp/EitherT.scala
Expand Up @@ -11,6 +11,20 @@ final case class EitherT[F[_], A, B](run: F[Either[A, B]]) {
def map[C](f: B => C)(implicit F: Functor[F]): EitherT[F, A, C] =
EitherT(F.map(run)(EitherCompat.map(_)(f)))

def ap[C](fa: EitherT[F, A, B => C])(implicit F: Applicative[F]): EitherT[F, A, C] =
EitherT(
F.ap(run)(F.map(fa.run) {
case fb @ Right(_) => {
case Right(b) =>
EitherCompat.map(fb)(fb => fb(b))
case l @ Left(_) =>
l.castR[C]
}
case l @ Left(_) => _ =>
l.castR[C]
})
)

def flatMap[C](f: B => EitherT[F, A, C])(implicit M: Monad[F]): EitherT[F, A, C] =
EitherT(
M.flatMap(run) {
Expand Down Expand Up @@ -42,16 +56,64 @@ final case class EitherT[F[_], A, B](run: F[Either[A, B]]) {

}

object EitherT extends EitherTMonadInstance
object EitherT extends EitherTMonadInstance {
def pure[F[_]: Applicative, A, B](b: B): EitherT[F, A, B] =
EitherT(implicitly[Applicative[F]].pure(Right(b)))

def pureLeft[F[_]: Applicative, A, B](a: A): EitherT[F, A, B] =
EitherT(implicitly[Applicative[F]].pure(Left(a)))
}

private trait EitherTFunctor[F[_], A] extends Functor[EitherT[F, A, ?]] {
implicit def F: Functor[F]

sealed abstract class EitherTMonadInstance {
implicit def eitherTMonad[F[_], A](implicit F0: Monad[F]): Monad[EitherT[F, A, ?]] = new Monad[EitherT[F, A, ?]] {
override def map[B, C](fa: EitherT[F, A, B])(f: B => C): EitherT[F, A, C] =
fa.map(f)(F)
}

private trait EitherTApplicative[F[_], A] extends Applicative[EitherT[F, A, ?]] with EitherTFunctor[F, A] {
implicit def F: Applicative[F]

override def pure[B](b: => B): EitherT[F, A, B] = EitherT(F.pure(Right(b)))

implicit val F: Monad[F] = F0
def pureLef[B](a: => A): EitherT[F, A, B] = EitherT(F.pure(Left(a)))

def flatMap[B, C](fa: EitherT[F, A, B])(f: B => EitherT[F, A, C]): EitherT[F, A, C] =
fa.flatMap(f)(F)
override def ap[B, C](fa: => EitherT[F, A, B])(fab: => EitherT[F, A, B => C]): EitherT[F, A, C] =
fa.ap(fab)(F)
}

private trait EitherTMonad[F[_], A] extends Monad[EitherT[F, A, ?]] with EitherTApplicative[F, A] {
implicit def F: Monad[F]
}

def pure[B](b: => B): EitherT[F, A, B] = EitherT(F.pure(Right(b)))
sealed abstract class EitherTFunctorInstance {
implicit def eitherTFunctor[F[_], A](implicit F0: Functor[F]): Functor[EitherT[F, A, ?]] = new EitherTFunctor[F, A] {
override implicit val F: Functor[F] = F0
}
}

sealed abstract class EitherTApplicativeInstance extends EitherTFunctorInstance {
implicit def eitherTFunctor[F[_], A](implicit F0: Applicative[F]): Applicative[EitherT[F, A, ?]] =
new EitherTApplicative[F, A] {
override implicit val F: Applicative[F] = F0
}
}

sealed abstract class EitherTMonadInstance extends EitherTApplicativeInstance {

implicit def eitherTMonad[F[_], A](implicit F0: Monad[F]): Monad[EitherT[F, A, ?]] = new EitherTMonad[F, A] {

override implicit val F: Monad[F] = F0

override def flatMap[B, C](ma: EitherT[F, A, B])(f: B => EitherT[F, A, C]): EitherT[F, A, C] =
ma.flatMap(f)(F)

}

implicit def eitherTEqual[F[_], A, B](implicit EQ: Equal[F[Either[A, B]]]): Equal[EitherT[F, A, B]] =
new Equal[EitherT[F, A, B]] {
override def equal(x: EitherT[F, A, B], y: EitherT[F, A, B]): Boolean =
EQ.equal(x.run, y.run)
}

}
98 changes: 98 additions & 0 deletions src/test/scala/just/fp/EitherTSpec.scala
@@ -0,0 +1,98 @@
package just.fp

import hedgehog._
import hedgehog.runner._

/**
* @author Kevin Lee
* @since 2019-09-19
*/
object EitherTSpec extends Properties {
override def tests: List[Test] = List(
property("testEitherTFunctorLaws", EitherTFunctorLaws.laws)
, property("testEitherTApplicativeLaws", EitherTApplicativeLaws.laws)
, property("testEitherTMonadLaws", EitherTMonadLaws.laws)
, property("testEitherTIdFunctorLaws", EitherTIdFunctorLaws.laws)
, property("testEitherTIdApplicativeLaws", EitherTIdApplicativeLaws.laws)
, property("testEitherTIdMonadLaws", EitherTIdMonadLaws.laws)
)

object EitherTFunctorLaws {
type EitherTOption[A] = EitherT[Option, String, A]

def genEitherT: Gen[EitherTOption[Int]] = Gens.genEitherT(Gens.genUnicodeString, Gens.genIntFromMinToMax)

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

object EitherTApplicativeLaws {
type EitherTOption[A] = EitherT[Option, String, A]

def genEitherT: Gen[EitherTOption[Int]] = Gens.genEitherT(Gens.genUnicodeString, Gens.genIntFromMinToMax)

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

object EitherTMonadLaws {
type EitherTOption[A] = EitherT[Option, String, A]

def genEitherT: Gen[EitherTOption[Int]] = Gens.genEitherT(Gens.genUnicodeString, Gens.genIntFromMinToMax)

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

object EitherTIdFunctorLaws {
type EitherTId[A] = EitherT[Id, String, A]

def genEitherT: Gen[EitherTId[Int]] = Gens.genEitherT(Gens.genUnicodeString, Gens.genIntFromMinToMax)

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

object EitherTIdApplicativeLaws {
type EitherTId[A] = EitherT[Id, String, A]

def genEitherT: Gen[EitherTId[Int]] = Gens.genEitherT(Gens.genUnicodeString, Gens.genIntFromMinToMax)

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

object EitherTIdMonadLaws {
type EitherTId[A] = EitherT[Id, String, A]

def genEitherT: Gen[EitherTId[Int]] = Gens.genEitherT(Gens.genUnicodeString, Gens.genIntFromMinToMax)

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

}
8 changes: 8 additions & 0 deletions src/test/scala/just/fp/Gens.scala
Expand Up @@ -229,4 +229,12 @@ object Gens {
a <- genA
} yield WriterT(F.pure((w, a)))

def genEitherT[F[_], A, B](genA: Gen[A], genB: Gen[B])(implicit F: Monad[F]): Gen[EitherT[F, A, B]] =
genEither(genA, genB).map {
case Right(b) =>
EitherT.pure(b)
case Left(a) =>
EitherT.pureLeft(a)
}

}

0 comments on commit 5312d5c

Please sign in to comment.