Skip to content

Instantly share code, notes, and snippets.

@tlipinski
Last active September 10, 2021 23:04
Show Gist options
  • Save tlipinski/8959a184b04fd3ebb41401ab0b759898 to your computer and use it in GitHub Desktop.
Save tlipinski/8959a184b04fd3ebb41401ab0b759898 to your computer and use it in GitHub Desktop.
Fixing composability
// We've got some very simple function which triples provided argument
def triple(a: Int): Int = 3 * a
// It composes nicely
triple(24) // val res0: Int = 72
triple(triple(24)) // val res1: Int = 216
// ---------------------------------------------------------------------------
// We want to add a check to this function which won't allow
// result to be greater than 100.
// We keep the function signature as before and handle error case with exception
def tripleEx(a: Int): Int = {
val result = 3 * a
if (result > 100) {
throw new IllegalStateException(s"Result is too big: ${result}")
} else {
result
}
}
// It composes nicely... but throws exceptions
tripleEx(24) // val res2: Int = 72
tripleEx(tripleEx(24)) // java.lang.IllegalStateException: Result is too big: 216
// ---------------------------------------------------------------------------
// Let's try a different way of handling errors by using Option type instead of exceptions.
// This will be expressed in function signature
def tripleOpt(a: Int): Option[Int] = {
val result = 3 * a
if (result > 100) {
None
} else {
Some(result)
}
}
tripleOpt(24) // val res3: Option[Int] = Some(72)
// Does not compile - composition is broken when we used Option for error handling:(
tripleOpt(tripleOpt(24)) // type mismatch;
// found : Option[Int]
// required: Int
// Can we modify function tripleOut so it is composable again?
// Signature of tripleOpt is Int => Option[Int]
// What if we could make it Option[Int] => Option[Int] like
def tripleOptFixed(opt: Option[Int]): Option[Int] =
opt match {
case Some(value) => tripleOpt(value)
case None => None
}
// It works! But we ended up with two functions for tripling:(
tripleOptFixed(Some(24)) // val res0: Option[Int] = Some(72)
tripleOptFixed(None) // val res1: Option[Int] = None
tripleOptFixed(tripleOpt(24)) // val res2: Option[Int] = None
// Instead of defining tripleOptFixed function let's fix tripleOpt function on the fly
// by defining a function which will convert tripleOpt to a needed function
def fix(f: Int => Option[Int]): (Option[Int] => Option[Int]) =
opt => opt match {
case Some(value) => f(value)
case None => None
}
// It works!
fix(tripleOpt)(Some(24)) // val res0: Option[Int] = Some(72)
fix(tripleOpt)(tripleOpt(10)) // val res1: Option[Int] = Some(90)
fix(tripleOpt)(tripleOpt(24)) // val res2: Option[Int] = None
// -----------------------------------------------------------------------
// Actually we can make function fix more generic and replace Int type with generic type A
// The body remains the same as before
def fix[A](f: A => Option[A]): (Option[A] => Option[A]) = ...
// Instead of returning a function literal we can push the opt parameter to
// another parameter list of fix function itself
def fix[A](f: A => Option[A])(opt: Option[A]): Option[A] = ...
// Just a small rearrangement of parameters - does it look familiar now?;)
def fix[A](opt: Option[A])(f: A => Option[A]): Option[A] = ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment