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 commentSenthil
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)
  }

}