I am in the process of building my own little functional programming library for educational purposes in scala 2.13, and while doing so, I am having trouble meeting the requirement for the map
operation for use in for-comprehensions.
Here, I am implementing map as a derived primitive using a combination of flatMap
and pure
, so that I don't have to provide an implementation for it each time I define a monad instance, but somehow, the compiler is not happy with that:
trait Monad[F[_]] {
def pure[A](a: A): F[A]
def flatMap[A, B](fa: F[A])(g: A => F[B]): F[B]
def map[A, B](fa: F[A])(f: A => B): F[B] =
flatMap(fa)(f andThen pure)
}
case class Id[A](get: A)
object Monad {
val idMonad = new Monad[Id] {
def pure[A](a: A) = Id(a)
def flatMap[A,B](ma: Id[A])(f: A => Id[B]) =
f(ma.get)
}
val example: Id[Unit] = for {
_ <- Id(())
} yield ()
Compiler error:
[error] value map is not a member of fp.Id[Unit]
[error] _ <- Id(())
[error] ^^^^^^
Error compiling project (Scala 2.13.13, JVM (17))
scala version: 2.13.13
Does anyone see any blatant mistake in what I've done that would not accept my definition for map
?
I have defined map as a method to the monad instance Id
, which enables the use of for-comprehension, but completely voids my intent of not having to provide an implementation for map
, given that pure
and flatMap
are implemented.
case class Id[A](get: A) {
def flatMap[B](f: A => Id[B]): Id[B] = f(get)
// Adding map here will satisfy the requirement for for-comprehensions.
def map[B](f: A => B): Id[B] = Id(f(get))
}
My goal is to have derived primitives such as map, as all Monads are Functors.
As mentioned in a comment, you did not implement the map
method on the Id
class. Notice furthermore that while your map
method takes an instance and two type parameters, for a map
method call to be synthesized by the compiler you would rather need a map
method defined on an instance.
What you have done so far is implementing part of the "type class pattern" in Scala. A common mechanism used to "bridge" between the type class instance and object instances is by using an implicit class
, as well as adding your type class instance in the implicit scope, like in the following example:
trait Monad[F[_]] {
def pure[A](a: A): F[A]
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
def map[A, B](fa: F[A])(f: A => B): F[B] =
flatMap(fa)(f andThen pure)
}
case class Id[A](get: A)
object Monad {
implicit class MonadOps[F[_], A](fa: F[A])(implicit val M: Monad[F]) {
def flatMap[B](f: A => F[B]): F[B] = M.flatMap[A, B](fa)(f)
def map[B](f: A => B): F[B] = M.map[A, B](fa)(f)
}
implicit val idMonad: Monad[Id] = new Monad[Id] {
def pure[A](a: A): Id[A] = Id(a)
def flatMap[A, B](ma: Id[A])(f: A => Id[B]): Id[B] =
f(ma.get)
}
}
import Monad._
val fourtytwo = for {
twentyone <- Id(21)
} yield twentyone * 2
assert(fourtytwo.get == 42)
You can play around with this code here on Scastie.
For more information on implicit classes, you can read this article on the Scala website.
You can read more about type classes on this article on the Typelevel blog.
Notice that this answer is valid for Scala 2. Scala 3 evolved everything revolving around implicits, including implicit classes. The introduction of extension methods to take their place lead to a few changes in how you work with type classes. If you're interested in type classes in Scala 3 the Scala website has an article about them.