Skip to content

Instantly share code, notes, and snippets.

@nbenns
Last active March 10, 2018 01:46
Show Gist options
  • Save nbenns/a8450739e6b6fefea43b8408b8c4af6f to your computer and use it in GitHub Desktop.
Save nbenns/a8450739e6b6fefea43b8408b8c4af6f to your computer and use it in GitHub Desktop.
Free
import scala.language.higherKinds
import cats.Functor
import cats.implicits._
/*
* A Free Functor is a Functor definition that doesn't care what F[_] is.
* We are Free from having to know about F[_] until we run() it.
* It uses the second Functor Law: fmap (g . f) = fmap g . fmap f
* FreeF is just a container that composes functions
* When you run it, you give it an actual container and the intial value
*/
case class FreeF[A, B](f: A => B) {
def run[F[_]](app: (F[A], A => B) => B)(x: F[A]): B = app(x, f)
}
object FreeF {
def apply[A]: FreeF[A, A] = FreeF(identity[A])
}
implicit def freeFunc[T]: Functor[FreeF[T, ?]] = new Functor[FreeF[T, ?]] {
override def map[A, B](fa: FreeF[T, A])(f: A => B) = FreeF[T, B](fa.f andThen f)
}
val effectfulOps: FreeF[Int, String] =
FreeF[Int]
.map(_ + 1)
.map(_.toString + "!")
val optionOps: Option[Int] => String = effectfulOps.run[Option]((v, f) => v match {
case Some(a) => f(a)
case None => "none"
})
optionOps(Some(2)) // 3!
val eitherOps: Either[String, Int] => String = effectfulOps.run[Either[String, ?]]((v, f) => v match {
case Right(a) => f(a)
case Left(b) => b
})
eitherOps(Right(1)) // 2!
// Custom data type - no functor instance is defined for this type
sealed trait Custom[+A]
case object Nope extends Custom[Nothing]
case class Yep[A](a: A) extends Custom[A]
val customOps: Custom[Int] => String = effectfulOps.run[Custom]((v, f) => v match {
case Yep(a) => f(a)
case Nope => "nope"
})
customOps(Yep(5))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment