Skip to main content

Functor

Functor

A functor is a typeclass for the types that can be mapped over.

Functor complies with identity law and composition law.

Identity Law

map(identity) === identity

import just.fp._
import just.fp.syntax._

Functor[Option].map(1.some)(identity(_)) === identity(1.some)
// res1: Boolean = true
Functor[Option].map(1.some)(identity(_))
// res2: Option[Int] = Some(value = 1)
identity(1.some)
// res3: Option[Int] = Some(value = 1)

Functor[Option].map(none[Int])(identity(_)) === identity(none[Int])
// res4: Boolean = true
Functor[Option].map(none[Int])(identity(_))
// res5: Option[Int] = None
identity(none[Int])
// res6: Option[Int] = None

Composition Law

map(f compose g) === map(f) compose map(g)

import just.fp._
import just.fp.syntax._

val f = (a: Int) => a + 100
// f: Int => Int = <function1>
val g = (b: Int) => b * 2
// g: Int => Int = <function1>

Functor[Option].map(1.some)(f compose g)
// res8: Option[Int] = Some(value = 102)

Functor[Option].map(Functor[Option].map(1.some)(g))(f)
// res9: Option[Int] = Some(value = 102)

Functor[Option].map(1.some)(f compose g) ===
Functor[Option].map(Functor[Option].map(1.some)(g))(f)
// res10: Boolean = true


Functor[Option].map(none[Int])(f compose g)
// res11: Option[Int] = None

Functor[Option].map(Functor[Option].map(none[Int])(g))(f)
// res12: Option[Int] = None

Functor[Option].map(none[Int])(f compose g) ===
Functor[Option].map(Functor[Option].map(none[Int])(g))(f)
// res13: Boolean = true

Examples

If there is a typeclass instance of Functor for a type A, map method can be used.

e.g.)

import just.fp._

final case class SomeType[A](a: A)

object SomeType {
implicit val functorA: Functor[SomeType] = new Functor[SomeType] {
override def map[A, B](fa: SomeType[A])(f: A => B): SomeType[B] =
SomeType(f(fa.a))
}
}

def times2(someType: SomeType[Int]): SomeType[Int] =
implicitly[Functor[SomeType]].map(someType)(_ * 2)

times2(SomeType(111))
// res15: SomeType[Int] = SomeType(a = 222)

There are existing Functor instances for Scala's Option, Either, List, Vector and Future.

import just.fp._
import just.fp.syntax._
// just.fp.syntax._ is only for .some, none, .right, .left

def foo[A, B, F[_] : Functor](fa: F[A])(f: A => B): F[B] =
Functor[F].map(fa)(f)
// or implicitly[Functor[F]].map(fa)(f)

foo(1.some)(_ * 2)
// res17: Option[Int] = Some(value = 2)
foo(none[Int])(_ * 2)
// res18: Option[Int] = None

foo(1.right[String])(_ * 2)
// res19: Either[String, Int] = Right(value = 2)
foo("error".left[Int])(_ * 2)
// res20: Either[String, Int] = Left(value = "error")

foo(List(1, 2, 3, 4, 5))(_ * 2)
// res21: List[Int] = List(2, 4, 6, 8, 10)
foo(List.empty[Int])(_ * 2)
// res22: List[Int] = List()

foo(Vector(1, 2, 3, 4, 5))(_ * 2)
// res23: Vector[Int] = Vector(2, 4, 6, 8, 10)
foo(Vector.empty[Int])(_ * 2)
// res24: Vector[Int] = Vector()

import scala.concurrent.Future
implicit val ec = scala.concurrent.ExecutionContext.global
// ec: concurrent.ExecutionContextExecutor = scala.concurrent.impl.ExecutionContextImpl$$anon$3@28da687b[Running, parallelism = 4, size = 1, active = 0, running = 0, steals = 2, tasks = 0, submissions = 0]

foo(Future(1))(_ * 2)
// res25: Future[Int] = Future(Success(2))