Skip to content

Instantly share code, notes, and snippets.

@deaktator
Last active January 2, 2020 17:03
Show Gist options
  • Save deaktator/c9222ee28764104dce68 to your computer and use it in GitHub Desktop.
Save deaktator/c9222ee28764104dce68 to your computer and use it in GitHub Desktop.
Type lambdas in Scala
// Also see: http://ktoso.github.io/scala-types-of-types/#type-lambda-span-style-color-red-span
import scala.language.higherKinds
//
// Notice Monad has one unbound type parameter.
//
trait Monad[M[_]] {
def point[A](a: A): M[A]
def map[A, B](m: M[A])(f: A => B): M[B] = flatMap(m)(f.andThen(point(_)))
def flatMap[A, B](m: M[A])(f: A => M[B]): M[B]
}
object Monad {
// Provides for comprehension syntax
implicit class Syntax[M[_], A](val m: M[A])(implicit M: Monad[M]) {
def map[B](f: A => B) = M.map(m)(f)
def flatMap[B](f: A => M[B]) = M.flatMap(m)(f)
}
// Option has one type parameter so we can just provide it as the parameter to the Monad trait.
implicit val optionMonad = new Monad[Option] {
def point[A](a: A): Option[A] = Option(a)
def flatMap[A, B](m: Option[A])(f: A => Option[B]): Option[B] = m.flatMap(f)
}
// Either[L, R] has two type parameters. Since Monad has only one type parameter, we
// need to curry one of the type parameters. Since there is a right-bias in the Scala
// community, when it comes to monads for Either, we curry bind the left type param, L.
// Then there is only one unbound typeparameter, R.
implicit def eitherRightBiasedMonad[L] = new Monad[({type Lambda[+R] = Either[L, R]})#Lambda] {
def point[R](r: R): Either[L, R] = Right(r)
// or just m.right.flatMap(f). Just did it this way to show we don't need to use Right's
// flatMap function.
def flatMap[R, B](m: Either[L, R])(f: R => Either[L, B]): Either[L, B] = m match {
case Left(l) => Left(l)
case Right(r) => f(r)
}
}
}
// import the syntax so we can use for comprehensions.
import Monad.Syntax
// Compute BMI. A function that takes monadic data.
def bmi[M[_]: Monad](heightInches: M[Double], massLbs: M[Float]) =
for {
h <- heightInches
m <- massLbs
} yield m / (h * h) * 703
// ================================================================================
// Example calls to bmi function
// ================================================================================
// Option[Double] = Some(27.12191358024691)
bmi(Option(72D), Option(200F))
// Option[Double] = None
bmi(Option.empty[Double], Option(200F))
// Option[Double] = None
bmi[Option](None, Option(200F))
// type inference needs help for Either's right-biased monad, but once a type
// hint is given, the implicits are found.
// scala.util.Either[String,Double] = Right(27.12191358024691)
bmi[({type Lambda[+R] = Either[String, R]})#Lambda](Right(72D), Right(200F))
// scala.util.Either[String,Double] = Left(unknown height)
bmi[({type Lambda[+R] = Either[String, R]})#Lambda](Left("unknown height"), Right(200F))
// ================================================================================
// kind-projector aims to make this syntax nicer via macros.
// See https://github.com/non/kind-projector for more information.
// ================================================================================
bmi[Either[String, +?]](Right(72D), Right(200F))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment