Reputation: 6611
we are creating our own OptionT
of cats for understanding, how monads are work and monads transformation flow. While creating our own custom monad getting some of the errors. First thing below is our code:
case class WhateverOpt[W[_], A] (value: W[Option[A]]) {
def map[B] (f: A => B) (implicit M: Monad[W]): WhateverOpt[W, B] =
WhateverOpt(M.map(value)(_.map(f)))
def flatMap[B] (f: A => WhateverOpt[W, B]) (implicit M: Monad[W]): WhateverOpt[W, B] =
WhateverOpt(M.flatMap(value)(optA => optA match {
case Some(v) => f(v).value
}))
}
implicit val optionTMonad = new Monad[Option] {
override def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa.map(f)
override def flatMap[A, B](fa: Option[A])(f: A => Option[B]): Option[B] = fa.flatMap(f)
}
val optionResult = for {
user <- WhateverOpt(repository.getUserOption(1))
addres <- WhateverOpt(repository.getAddressOption(user))
} yield addres.city
Below are the points, where we stuck:
WhateverOpt
flatMap
method?Error:(26, 12) could not find implicit value for parameter M: usercases.mtransfomer.Monad[scala.concurrent.Future]
addres <- WhateverOpt(repository.getAddressOption(user))
We are not sure about the error because we are creating optionTMonad
implicit and by default, all are in the same scope. How can we resolve these two issues?
Update
Full code is available on Github branch https://github.com/harmeetsingh0013/fp_scala/blob/master/src/main/scala/usercases/mtransfomer/Example5.scala
Upvotes: 2
Views: 470
Reputation: 108111
How to handle None case in WhateverOpt flatMap method?
When you flatMap
over None
you return None
. When you flatMap
over WhateverOpt[W[_], B]
you want to return the pure
of it, which in your code would be M.pure(None)
.
When executing the code, getting runtime error:
Error:(26, 12) could not find implicit value for parameter M: usercases.mtransfomer.Monad[scala.concurrent.Future] addres <- WhateverOpt(repository.getAddressOption(user))
That's a compile-time error (not a runtime one) and it's due to the missing instance of Monad[Future]
. In order to get an instance of Monad[Future]
in scope with cats, you can do:
import cats.instances.future._
import scala.concurrent.ExecutionContext.Implicits.global
Also, you can avoid declaring your own Monad[Option]
by importing it from cats with
import cats.instances.option._
Upvotes: 1
Reputation: 6611
How to handle None case in WhateverOpt flatMap method?
This answer is already explained by @Gabriele Petronella and @Andrey Tyukin with details.
When executing the code, getting runtime error: Error:(26, 12) could not find implicit value for parameter M: usercases.mtransfomer.Monad[scala.concurrent.Future] addres <- WhateverOpt(repository.getAddressOption(user))
This error occurs, because In WhateverOpt
constructor we know that our value is W[Option[A]]
, where Option
is already defined and handle by the code, but repository.getUserOption(1)
return Future[Option[User]]
where Future
is handled by generic parameter W
and in that, case, we need to define, how to handle monads for Future
. For resolving that issue, we need to implement new Monad[Future]
rather than, new Monad[Option]
as below:
case class WhateverOpt[W[_], A] (value: W[Option[A]]) {
def map[B] (f: A => B) (implicit M: Monad[W]): WhateverOpt[W, B] =
WhateverOpt(M.map(value)(_.map(f)))
def flatMap[B] (f: A => WhateverOpt[W, B]) (implicit M: Monad[W]): WhateverOpt[W, B] =
WhateverOpt(M.flatMap(value)(optA => optA match {
case Some(v) => f(v).value
case None => M.pure(None)
}))
}
implicit val futureMonad = new Monad[Future] {
override def pure[A](a: A): Future[A] = Future.successful(a)
override def map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f)
override def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f)
}
val optionResult: WhateverOpt[Future, String] = for {
user <- WhateverOpt(repository.getUserOption(1))
addres <- WhateverOpt(repository.getAddressOption(user))
} yield addres.city
I am not sure about my disription, which I mention in answere, but current my assumptions are this and for me above code is working fine. For a complete example, please click on the GitHub repo, which is mention in the question.
Upvotes: 1
Reputation: 44918
About how to deal with None
:
case class WhateverOpt[W[_], A] (value: W[Option[A]]) {
def map[B] (f: A => B) (implicit M: Monad[W]): WhateverOpt[W, B] =
WhateverOpt(M.map(value)(_.map(f)))
def flatMap[B]
(f: A => WhateverOpt[W, B])
(implicit wMonad: Monad[W])
: WhateverOpt[W, B] = {
WhateverOpt(wMonad.flatMap(value) { (oa: Option[A]) =>
oa match {
case None => wMonad.pure(None)
case Some(a) => f(a).value
}
})
}
}
Imagine for a second that W
is Future
. Then the above code says:
value
yields a result oa
of type Option[A]
oa
turns out to be None
, then there is nothing we can do, because we cannot obtain any instances of type A
in order to call f
. Therefore, immediately return None
. The immediately return
is the pure
-method of the Future
-monad, so for the general case we have to invoke wMonad.pure(None)
.oa
yields Some(a)
, we can give this a
to f
, and then immediately unpack it to get to the value
of type W[Option[B]]
. W[Option[B]]
(whether empty
or not), we can wrap it into WhateverOpt
and return from the flatMap
method.I assume that you wanted to reimplement Monad[Option]
just for fun (it's already in the library (the catsStdInstancesForOption
thing is a CommutativeMonad
), but here is how you could re-build it:
implicit val optionTMonad = new Monad[Option] {
override def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa.map(f)
def flatMap[A, B](fa: Option[A])(f: A => Option[B]): Option[B] = fa.flatMap(f)
def pure[A](a: A): Option[A] = Some(a)
def tailRecM[A, B](a: A)(f: (A) => Option[Either[A, B]]): Option[B] = {
f(a) match {
case Some(Left(nextA)) => tailRecM(nextA)(f)
case Some(Right(res)) => Some(res)
case None => None
}
}
}
Notice that 1.0.1 requires to implement pure
and tailRecM
, and does not provide default implementations for that.
I don't want to say much about the necessary imports for future
, but the latest version has cats.instances.future which provides a Monad
instance. Check this again, because it seems as if you are using a different version of cats (your version didn't complain because of the missing tailRecM
in your Option
-monad).
Upvotes: 1