0
votes

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?

1
All your code is structured in a very weird way. May I ask, what is your end goal? - Luis Miguel Mejía Suárez
I was following instructions from [here] (scalac.io/typeclasses-in-scala) to implement typeclass. Let's say I wanted to implement liftA2 function under applicative typeclass which needs fmap and apply, I can do something like 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. - Senthil
This code require you to correctly infer all arguments at once, while you actually provide them to functions much later - which allows compiler to infer things like Functor[F, Nothing, Nothing], and then A and B 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 Kubuszok
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 }. Apologies for the formatting, I'm not able to format my comment - Senthil
Thanks @LuisMiguelMejíaSuárez I guess you are right about this. And looks like subtyping vs context bounds is still open with both the libraries [typelevel.org/blog/2016/09/30/subtype-typeclasses.html] - Senthil

1 Answers

0
votes

Editing the above code as per @Mateusz Kubuszok comments, worked fine for me.


trait Functor[F[_]] {
  //def fmap[A, B] : (A => B) => F[A] => F[B]
  def fmap[A, B](f: A => B, fa: F[A]): F[B]
}

object Functor {

  //def fmap[A, B, F[_]] (implicit ev: Functor[F]) = ev.fmap
  def fmap[A, B, F[_]](f: A => B, fa: F[A]) (implicit ev: Functor[F]) = ev.fmap(f, fa)

}

trait Applicative[F[_]] {
  //def pure[A]: A => F[A]
  def pure[A](a: A) : F[A]

  //def apply[A, B]: F[A => B] => F[A] => F[B]
  def appl[A, B](f: F[A=>B], fa: F[A]): F[B]
}

object Applicative {

  //def pure[A, F[_]: Applicative]: A => F[A] = implicitly[Applicative[F]].pure
  def pure[A, F[_]](a: A)(implicit ev: Applicative[F]) = ev.pure(a)

  //def apply[A, B, F[_]: Applicative]: F[A => B] => F[A] => F[B] = implicitly[Applicative[F]].apply
  
  def appl[A, B, F[_]] (f: F[A => B], fa: F[A])(implicit ev: Applicative[F]): F[B]=
    ev.appl(f, fa)

  def liftA2[A, B, C, F[_]: Functor: Applicative] (f: (A => B => C), fa: F[A], fb: F[B]): F[C] =
    implicitly[Applicative[F]].appl(implicitly[Functor[F]].fmap(f, fa), fb)

}

trait Monad[M[_]] {

  //def bind[A, B]: M[A] => (A => M[B]) => M[B]
  def bind[A, B](ma: M[A], f: A => M[B]): M[B]

  //def ret[A]: A => M[A]
  def ret[A](a: A): M[A]

}

object Monad {
 // def bind[A, B, M[_] :Monad]: M[A] => (A => M[B]) => M[B] = implicitly[Monad[M]].bind
  def bind[A, B, M[_] :Monad](ma: M[A], f: (A => M[B])): M[B] = implicitly[Monad[M]].bind(ma, f)
  //def ret[A, M[_] :Monad]: A => M[A] = implicitly[Monad[M]].ret
  def ret[A, M[_] :Monad](a: A)(implicit ev: Monad[M]): M[A] = ev.ret(a)
}


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](f: (A => B), ma: Maybe[A]): Maybe[B] =
      ma match {
        case Some(a) => Maybe(f(a))
        case _ => None.apply
      }

    override def pure[A](a: A) : Maybe[A] = Some(a)

    override def ret[A](a: A) : Maybe[A] = Some(a)

    override def appl[A, B] (mab: Maybe[A => B], ma: Maybe[A]): Maybe[B] = mab match {
      case Some(f) => fmap(f, ma)
      case _ => None.apply
    }

    override def bind[A, B](ma: Maybe[A], f: (A => Maybe[B])) : Maybe[B] = ma match {
      case Some(a) => f(a)
      case _ => None.apply
    }
  }
}

import io.github.senthilganeshs.typeclass.Monad.bind
import io.github.senthilganeshs.typeclass.Functor.fmap
import io.github.senthilganeshs.types.Maybe

object Main {

  def main(args: Array[String]): Unit = {
    import io.github.senthilganeshs.types.Maybe._
    val a: Maybe[Int] = Maybe(10)

    val p: Maybe[String] = bind(Maybe(10), (i: Int) => Maybe(s"$i values"))

    val q: Maybe[String] = fmap((i: String) => s"$i !!", p)

    println(p)
    println(q)
  }

}