Skip to content

Commit

Permalink
Merge pull request #138 from Kevin-Lee/task/132/add-optiont
Browse files Browse the repository at this point in the history
Close #132 - Add OptionT
  • Loading branch information
kevin-lee committed Nov 29, 2019
2 parents f5601c0 + 70ce120 commit d3eb5f5
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 0 deletions.
106 changes: 106 additions & 0 deletions src/main/scala/just/fp/OptionT.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package just.fp

import just.fp.syntax.{implicitToSome, none}

/**
* @author Kevin Lee
* @since 2019-11-29
*/
final case class OptionT[F[_], A](run: F[Option[A]]) {
def map[B](f: A => B)(implicit F: Functor[F]): OptionT[F, B] =
OptionT(F.map(run)(_.map(f)))

def ap[B](fa: OptionT[F, A => B])(implicit F: Applicative[F]): OptionT[F, B] =
OptionT(
F.ap(run)(F.map(fa.run) {
case Some(ab) =>
{
case Some(a) =>
ab(a).some
case None =>
none[B]
}
case None =>
_ => none[B]
})
)

def flatMap[B](f: A => OptionT[F, B])(implicit M: Monad[F]): OptionT[F, B] =
OptionT(
M.flatMap(run) {
case Some(a) =>
f(a).run
case None =>
M.pure(none[B])
}
)

def isDefined(implicit F: Functor[F]): F[Boolean] =
F.map(run)(_.isDefined)

def isEmpty(implicit F: Functor[F]): F[Boolean] =
F.map(run)(_.isEmpty)

}

object OptionT extends OptionTMonadInstance {
def pure[F[_]: Applicative, A](a: => A): OptionT[F, A] =
OptionT(Applicative[F].pure(a.some))

def some[F[_]: Applicative, A](a: => A): OptionT[F, A] = pure(a)

def none[F[_]: Applicative, A]: OptionT[F, A] =
OptionT(Applicative[F].pure(None))
}

private trait OptionTFunctor[F[_]] extends Functor[OptionT[F, *]] {
implicit def F: Functor[F]

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

private trait OptionTApplicative[F[_]] extends Applicative[OptionT[F, *]] with OptionTFunctor[F] {
implicit def F: Applicative[F]

override def pure[A](a: => A): OptionT[F, A] = OptionT(F.pure(a.some))

override def ap[A, B](fa: => OptionT[F, A])(fab: => OptionT[F, A => B]): OptionT[F, B] =
fa.ap(fab)(F)
}

private trait OptionTMonad[F[_]] extends Monad[OptionT[F, *]] with OptionTApplicative[F] {
implicit def F: Monad[F]
}

sealed abstract class OptionTFunctorInstance {
implicit def OptionTFunctor[F[_]](implicit F0: Functor[F]): Functor[OptionT[F, *]] = new OptionTFunctor[F] {
override implicit val F: Functor[F] = F0
}
}

sealed abstract class OptionTApplicativeInstance extends OptionTFunctorInstance {
implicit def OptionTApplicative[F[_]](implicit F0: Applicative[F]): Applicative[OptionT[F, *]] =
new OptionTApplicative[F] {
override implicit val F: Applicative[F] = F0
}
}

sealed abstract class OptionTMonadInstance extends OptionTApplicativeInstance {

implicit def OptionTMonad[F[_]](implicit F0: Monad[F]): Monad[OptionT[F, *]] = new OptionTMonad[F] {

override implicit val F: Monad[F] = F0

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

}

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

}
8 changes: 8 additions & 0 deletions src/test/scala/just/fp/Gens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,14 @@ object Gens {
a <- genA
} yield WriterT(F.pure((w, a)))

def genOptionT[F[_], A](genA: Gen[A])(implicit F: Monad[F]): Gen[OptionT[F, A]] =
genOption(genA).map {
case Some(a) =>
OptionT.some(a)
case None =>
OptionT.none
}

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) =>
Expand Down
97 changes: 97 additions & 0 deletions src/test/scala/just/fp/OptionTSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package just.fp

import hedgehog._
import hedgehog.runner._

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

type EitherStringOr[A] = Either[String, A]
type OptionTEither[A] = OptionT[EitherStringOr, A]
type OptionTId[A] = OptionT[Id, A]

override def tests: List[Test] = List(
property("testOptionTFunctorLaws", OptionTFunctorLaws.laws)
, property("testOptionTApplicativeLaws", OptionTApplicativeLaws.laws)
, property("testOptionTMonadLaws", OptionTMonadLaws.laws)
, property("testOptionTIdFunctorLaws", OptionTIdFunctorLaws.laws)
, property("testOptionTIdApplicativeLaws", OptionTIdApplicativeLaws.laws)
, property("testOptionTIdMonadLaws", OptionTIdMonadLaws.laws)
)

object OptionTFunctorLaws {

def genOptionT: Gen[OptionTEither[Int]] = Gens.genOptionT[EitherStringOr, Int](Gens.genIntFromMinToMax)

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

object OptionTApplicativeLaws {

def genOptionT: Gen[OptionTEither[Int]] = Gens.genOptionT[EitherStringOr, Int](Gens.genIntFromMinToMax)

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

object OptionTMonadLaws {

def genOptionT: Gen[OptionTEither[Int]] = Gens.genOptionT[EitherStringOr, Int](Gens.genIntFromMinToMax)

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

object OptionTIdFunctorLaws {

def genOptionT: Gen[OptionTId[Int]] = Gens.genOptionT(Gens.genIntFromMinToMax)

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

object OptionTIdApplicativeLaws {

def genOptionT: Gen[OptionTId[Int]] = Gens.genOptionT(Gens.genIntFromMinToMax)

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

object OptionTIdMonadLaws {

def genOptionT: Gen[OptionTId[Int]] = Gens.genOptionT(Gens.genIntFromMinToMax)

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

}

0 comments on commit d3eb5f5

Please sign in to comment.