Last active
September 10, 2021 23:04
-
-
Save tlipinski/8959a184b04fd3ebb41401ab0b759898 to your computer and use it in GitHub Desktop.
Fixing composability
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
// 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