I was trying to apply typeclass pattern in scala and tried implementing Functor, Applicative and Monad typeclasses as follows
trait Functor[F[_]] {
def fmap[A, B] : (A => B) => F[A] => F[B]
}
object Functor {
def fmap[A, B, F[_]] (implicit ev: Functor[F]) = ev.fmap
}
trait Applicative[F[_]] {
def pure[A]: A => F[A]
def apply[A, B]: F[A => B] => F[A] => F[B]
}
object Applicative {
def pure[A, F[_]: Applicative]: A => F[A] = implicitly[Applicative[F]].pure
def apply[A, B, F[_]: Applicative]: F[A => B] => F[A] => F[B] = implicitly[Applicative[F]].apply
def liftA2[A, B, C, F[_]: Functor: Applicative]: (A => B => C) => F[A] => F[B] => F[C] =
f => fa => fb => implicitly[Applicative[F]].apply(implicitly[Functor[F]].fmap(f)(fa))(fb)
}
trait Monad[M[_]] {
def bind[A, B]: M[A] => (A => M[B]) => M[B]
def ret[A]: A => M[A]
}
object Monad {
def bind[A, B, M[_] :Monad]: M[A] => (A => M[B]) => M[B] = implicitly[Monad[M]].bind
def ret[A, M[_] :Monad]: A => M[A] = implicitly[Monad[M]].ret
}
sealed trait Maybe[A]
case class Some[A](value: A) extends Maybe[A]
case object None extends Maybe[Void] {
def apply[A]: Maybe[A] = None.asInstanceOf[Maybe[A]]
}
object Maybe {
def apply[A] (value: A): Maybe[A] = Some(value)
implicit object MaybeOps extends Functor[Maybe] with Applicative[Maybe] with Monad[Maybe] {
override def fmap[A, B]: (A => B) => Maybe[A] => Maybe[B] =
fn => ma => ma match {
case Some(a) => Maybe(fn(a))
case _ => None.apply
}
override def pure[A]: A => Maybe[A] = Some(_)
override def ret[A]: A => Maybe[A] = Some(_)
override def apply[A, B]: Maybe[A => B] => Maybe[A] => Maybe[B] = mab => ma => mab match {
case Some(f) => fmap(f)(ma)
case _ => None.apply
}
override def bind[A, B]: Maybe[A] => (A => Maybe[B]) => Maybe[B] = ma => f => ma match {
case Some(a) => f(a)
case _ => None.apply
}
}
}
And the consumer logic where I get to apply bind, fmap functions on Maybe datatype does not typecheck:
val a: Maybe[Int] = Maybe(10)
val p: Maybe[String] = bind.apply(Maybe(10))((i: Int) => Maybe(s"$i values"))
val q = fmap.apply(i => s"$i !!")(p)
println(p)
println(q)
The above code getting the error as
Error: (15, 29) type mismatch; found: String required: Nothing val q = fmap.apply(i => s"$i !!")(p)
I didn't want to extend the trait to make it work. Since the context-bounds specify the requirement of Monad typeclass to require an instance of Applicative in scope. Is there any clean way to support this in scala?
def liftA2[A, B, C, F[_]: Functor :Applicative]: (A => B => C) => F[A] => F[B] => F[C] = f => fa => fb => implicitly[Applicative[F]].apply(implicitly[Functor[F]].fmap(f)(fa))(fb)
without requiring Applicative typeclass to extend Functor. But the consumer logic does not compile even though it seems valid. – SenthilFunctor[F, Nothing, Nothing]
, and thenA
andB
just don't match as they are provided when the types got already interred (badly). There is a reason why nobody uses inference this way - this article also avoids it. – Mateusz Kubuszoktrait Functor[F[_]] { def fmap[A, B] : (A => B) => F[A] => F[B] } object Functor { def fmap[A, B, F[_]] (implicit ev: Functor[F]) = ev.fmap }
. Apologies for the formatting, I'm not able to format my comment – Senthil