Skip to content

Instantly share code, notes, and snippets.

@dhinojosa
Created January 14, 2021 22:23
Show Gist options
  • Save dhinojosa/22b4d3e0b7f0c6cbfc6546d6bb4ec466 to your computer and use it in GitHub Desktop.
Save dhinojosa/22b4d3e0b7f0c6cbfc6546d6bb4ec466 to your computer and use it in GitHub Desktop.
Higher Kinds and Functors in Scala 3
package com.xyzcorp.demo.higherkindedtypes
trait Functor[F[_]]:
def fmap[A,B](fa:F[A])(f: A => B):F[B]
object Functor:
def apply[F[_]](using fun:Functor[F]):Functor[F] = fun
object ListTypeClasses:
given Functor[List] =
new Functor[List]:
def fmap[A,B](fa:List[A])(f:A => B) =
fa.map(f)
case class MyBox[A](value:A)
object MyBox:
given Functor[MyBox] =
new Functor[MyBox]:
def fmap[A,B](ba:MyBox[A])(f:A => B) =
new MyBox(f(ba.value))
object EitherTypeClasses:
given Functor[[A] =>> Either[String,A]] =
new Functor[[A] =>> Either[String,A]]:
def fmap[A,B](se:Either[String,A])(f:A => B) =
se match
case Left(x) => Left(x)
case Right(y) => Right(f(y))
object UsingHigherKindedTypes:
@main def assertUsingAHigherKindedTypeWorksWithList:Unit =
import ListTypeClasses.{given}
val result:List[Int] = Functor[List].fmap(List(1,2,3))(x => x * 2)
println(result)
@main def assertUsingAHigherKindedTypeWorksWithCustom:Unit =
import MyBox.{given}
val result:MyBox[Int] = Functor[MyBox].fmap(MyBox("Hello"))(x => x.length)
println(result)
@main def assertUsingWithAnEither:Unit =
import EitherTypeClasses.{given}
val result = Functor[[A] =>> Either[String,A]].fmap(Right(30))(x => x * 2)
println(result)
end UsingHigherKindedTypes
@deanwampler
Copy link

Fun example! Here are some changes you might consider:

// From https://gist.github.com/dhinojosa/22b4d3e0b7f0c6cbfc6546d6bb4ec466

package com.xyzcorp.demo.higherkindedtypes

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

object Functor:
  def apply[F[_]](using fun:Functor[F]):Functor[F] = fun

object ListTypeClasses:
  given Functor[List] with      // More concise way than Functor[List] = new FunctorList
    def fmap[A,B](fa:List[A])(f:A => B) =
      fa.map(f)

case class MyBox[A](value:A)
object MyBox:
   given Functor[MyBox] with
     def fmap[A,B](ba:MyBox[A])(f:A => B) =
        new MyBox(f(ba.value))


object EitherTypeClasses:
  given Functor[[A] =>> Either[String,A]] with
    def fmap[A,B](se:Either[String,A])(f:A => B) =
      se match
        case Left(x) => Left(x)
        case Right(y) => Right(f(y))

// The object is actually ignored when constructing the fully
// qualified path! Try runMain com.xyzcorp.demo.higherkindedtypes.assertUsingAHigherKindedTypeWorksWithCustom
// with and without the UsingHigherKindedTypes object!
//object UsingHigherKindedTypes:
@main def assertUsingAHigherKindedTypeWorksWithList:Unit =
  import ListTypeClasses.given   // No need for the braces around {given}
  val result:List[Int] = Functor[List].fmap(List(1,2,3))(x => x * 2)
  println(result)

@main def assertUsingAHigherKindedTypeWorksWithCustom:Unit =
  import MyBox.given
  val result:MyBox[Int] = Functor[MyBox].fmap(MyBox("Hello"))(x => x.length)
  println(result)

@main def assertUsingWithAnEither:Unit =
  import EitherTypeClasses.given
  val result = Functor[[A] =>> Either[String,A]].fmap(Right(30))(x => x * 2)
  println(result)

@main def all:Unit =
  import ListTypeClasses.given   // None collide, so you could define all the givens in one object, too.
  import MyBox.given
  import EitherTypeClasses.given

  val result1:List[Int] = Functor[List].fmap(List(1,2,3))(x => x * 2)
  println(result1)

  val result2:MyBox[Int] = Functor[MyBox].fmap(MyBox("Hello"))(x => x.length)
  println(result2)

  val result3 = Functor[[A] =>> Either[String,A]].fmap(Right(30))(x => x * 2)
  println(result3)


// end UsingHigherKindedTypes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment