Last active
January 2, 2020 17:03
-
-
Save deaktator/c9222ee28764104dce68 to your computer and use it in GitHub Desktop.
Type lambdas in Scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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