Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to use typeclass inheritance?

trait Functor[F[_]]:
  extension[A] (fa: F[A])
    def map[B](f: A => B): F[B]

trait Applicative[F[_]: Functor]:
  def pure[A](a: A): F[A]

  extension[A, B] (fab: F[A => B])
    def ap(fa: F[A]): F[B]

object Applicative:
  def pure[A, F[_]](a: A)(using ap: Applicative[F]): F[A] = ap.pure(a)

@targetName("apFirst")
def <*[A, B, F[_]: Applicative](fa: F[A], fb: F[B]): F[A] = fa.map(a => (_: B) => a).ap(fb)

Error: value map is not a member of type F in <*.

I get error in the last function. If I add Functor constraint it works, but shouldn’t it work without that since I constrained it in the Applicative definition? What am I missing?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

Type class inheritance is a normal inheritance:

trait Functor[F[_]]:
  extension[A] (fa: F[A])
    def map[B](f: A => B): F[B]

trait Applicative[F[_]] extends Functor[F]:
  def pure[A](a: A): F[A]

  extension[A, B] (fab: F[A => B])
    def ap(fa: F[A]): F[B]
  
  extension[A] (fa: F[A])
    override def map[B](f: A => B): F[B] = pure(f).ap(fa)

Be cautious when you use it though, as having 2 type classes having the same parent will result in conflicting instances:

trait TypeClass1[A]
  def method: A
object TypeClass1:
  def method[A: TypeClass1]: A = summon[TypeClass1[A]].method

trait TypeClass2[A] extends TypeClass1[A]:
  def method2: A
  override def method: A = method2

trait TypeClass3[A] extends TypeClass1[A]:
  def method3: A
  override def method: A = method3

def usage[A: TypeClass2: TypeClass3] =
  TypeClass1.method[A] // ambiguous implicit

This issue can be seen in some Cats Effect 2 type class hierarchy.

When you define things like this:

trait Functor[F[_]]:
  extension[A] (fa: F[A])
    def map[B](f: A => B): F[B]

trait Applicative[F[_]: Functor]:
  def pure[A](a: A): F[A]

  extension[A, B] (fab: F[A => B])
    def ap(fa: F[A]): F[B]

then the inside of Applicative[F] sees that F is a Functor and has access to .map… but F : Functor is a constraint, not a proper type bound. It desugars to (using Functor[F]) which is merely an argument to a constructor (or other method), not something which extends interface nor encodes in its type any information about inheritance. As far as the outside of Applicative is concerned in such code there is no relationship between Functor[F] and Applicative[F].

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading