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)