Skip to content

Instantly share code, notes, and snippets.

@dacr
Last active April 2, 2023 10:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dacr/ffa5e779d1fa5cb29537fddde754edfe to your computer and use it in GitHub Desktop.
Save dacr/ffa5e779d1fa5cb29537fddde754edfe to your computer and use it in GitHub Desktop.
playing with functors / published by https://github.com/dacr/code-examples-manager #9134c469-c58a-41b6-b024-6ff21fd4ce69/ee5ef3fb09a729358f36e686461681b425707661
// summary : playing with functors
// keywords : scala, functor, pure-functional, @testable
// publish : gist
// authors : David Crosson
// license : Apache NON-AI License Version 2.0 (https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-APACHE2)
// id : 9134c469-c58a-41b6-b024-6ff21fd4ce69
// created-on : 2022-01-22T17:15:13+01:00
// managed-by : https://github.com/dacr/code-examples-manager
// run-with : scala-cli $file
// ---------------------
//> using scala "3.1.1"
// ---------------------
import scala.util.{Try, Success}
// ------------------------------------------------------------
// Inspired from "What the Functor? | Functors in Scala | Rock the JVM"
// https://www.youtube.com/watch?v=aSnY2JBzjUw&ab_channel=RocktheJVM
// ------------------------------------------------------------
// Definitions
// - https://typelevel.org/cats/typeclasses/functor.html :
// Functor is a type class that abstracts over type constructors that can be map‘ed over
// - https://dzone.com/articles/functors-in-scala
// A functor is a type of mapping between categories
// ------------------------------------------------------------
// A functor is defined by a type Constructor F[_] together with a function
// map :(A=>B) => F[A]=>F[B]
// ------------------------------------------------------------
val anIncrementedList = List(1, 2, 3).map(_ + 1)
val anOption = Some(2)
val aTry = Success(42)
val aTransformedOption = anOption.map(_ * 10)
val aTransformedTry = aTry.map(_ * 10)
// Functors
def do10xList(that: List[Int]): List[Int] = that.map(_ * 10)
def do10xOption(that: Option[Int]): Option[Int] = that.map(_ * 10)
def do10xTry(that: Try[Int]): Try[Int] = that.map(_ * 10)
// => Duplicated code here !
// Let's generalize thanks to Functor
trait Functor[C[_]]:
def map[A, B](container: C[A])(f: A => B): C[B]
given listFunctor: Functor[List] with
override def map[A, B](container: List[A])(f: A => B): List[B] = container.map(f)
//generalize function with a higher kind type => "stable" API
def do10x[C[_]](container: C[Int])(using functor: Functor[C]): C[Int] =
functor.map(container)(_ * 10)
do10x(List(1, 2, 3))(using listFunctor)
do10x(List(1, 2, 3))
// ------------------------------------------------------------
sealed trait Tree[+T]
object Tree:
def leaf[T](value: T): Tree[T] = Leaf(value)
def branch[T](value: T, left: Tree[T], right: Tree[T]): Tree[T] = Branch(value, left, right)
case class Leaf[+T](value: T) extends Tree[T]
case class Branch[+T](value: T, left: Tree[T], right: Tree[T]) extends Tree[T]
given treeFunctor: Functor[Tree] with
override def map[A, B](container: Tree[A])(f: A => B): Tree[B] =
container match
case Leaf(value) => Leaf(f(value))
case Branch(value, l, r) => Branch(f(value), map(l)(f), map(r)(f))
import Tree.*
val tree =
branch(1, branch(2, leaf(3), leaf(4)), leaf(5))
println(do10x(tree))
// ------------------------------------------------------------
extension [C[_], A, B](container: C[A])(using functor: Functor[C])
def map(f: A=>B) = functor.map(container)(f)
val tenxTree = tree.map(_ * 10) // treeFunctor.map(tree)(_ * 10)
// it extends the original data type
println(tenxTree)
// ------------------------------------------------------------
println(
"""
Functors allows us to generalize an API to any kind of data structure
in a uniform way without having to repeat ourself !
"""
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment