Encoding type class hierarchies

Perhaps I’m clutching at straws here, but I just tried this and found it quite pleasing:

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

trait Apply[F[+_]] extends Functor[F]:
  def ap[A, B](f: F[A => B]): F[A] => F[B]

trait Applicative[F[+_]] extends Apply[F]:
  def pure[A](x: A): F[A]

enum Maybe[+A]:
  case Nothing
  case Just(value: A)

object Maybe:
  given functor: Functor[Maybe] with
    def map[A, B](f: A => B) =
      case Nothing => Nothing
      case Just(x) => Just(f(x))

  given apply: Apply[Maybe] with
    export functor.*
    def ap[A, B](f: Maybe[A => B]) =
      (f, _) match
        case (Just(f), Just(x)) => Just(f(x))
        case _                  => Nothing

  given applicative: Applicative[Maybe] with
    export apply.*
    def pure[A](x: A) = Just(x)

Edit on Scastie

2 Likes