Last active
August 18, 2016 22:16
-
-
Save george-hawkins/6b2f7034f79c244ad232b1f20691438f to your computer and use it in GitHub Desktop.
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
package mytests | |
import org.scalatest.FunSuite | |
class MonadLawSuite extends FunSuite { | |
// 1. Remind ourselves about associativity. | |
test("addition is associative") { | |
val rtl = (1 + 2) + 3 | |
val ltr = 1 + (2 + 3) | |
assert(rtl === ltr) | |
} | |
// According to the Monad Laws bind must be associative. | |
// In Scala bind is called flatMap - so... | |
// | |
// (m flatMap f) flatMap g == what? | |
// | |
// Well what does it equal? If we looked at out addition example above we might naively write: | |
// | |
// (m flatMap f) flatMap g == m flatMap (f flatMap g) | |
// | |
// But the second flatMap on the LHS clearly expects to operate on a sequence, while on the RHS it's operating on a function. | |
// | |
// Instead we end up writing: | |
// | |
// (m flatMap f) flatMap g == m flatMap (x => f(x) flatMap g) | |
// | |
// Just to be clear about how things are grouping here I'll add parenthesis around the anonymous function body: | |
// | |
// (m flatMap f) flatMap g == m flatMap (x => (f(x) flatMap g)) | |
// OK - lets creates a specific example and see if we can demonstrate this aspect of things with lists... | |
// We create a book class - a book has a title and a list of one or more authors. | |
case class Book(title: String, authors: List[String]) | |
// We create a list of books. | |
val books: List[Book] = List( | |
Book(title = "Structure and Interpretation of Computer Programs", | |
authors = List("Abelson, Harald", "Sussman, Gerald J.")), | |
Book(title = "Introduction to Functional Programming", | |
authors = List("Bird, Richard", "Wadler, Phil")), | |
Book(title = "Effective Java", | |
authors = List("Bloch, Joshua")), | |
Book(title = "Java Puzzlers", | |
authors = List("Bloch, Joshua", "Gafter, Neal")), | |
Book(title = "Programming in Scala", | |
authors = List("Odersky, Martin", "Spoon, Lex", "Venners, Bill"))) | |
// We define two functions. | |
val toAuthors: Book => List[String] = b => b.authors | |
val toUpperLetters: String => List[Char] = s => s.toUpperCase.toList | |
// 2. Let's try first with function values. | |
test("flatMap with function values is associative") { | |
val ltr = (books flatMap toAuthors) flatMap toUpperLetters | |
val rtl = books flatMap (b => (b.authors flatMap toUpperLetters)) | |
// I couldn't use toAuthors in the rtl case - I had to expand it out into an anonymous function. | |
// Although I could have used toAuthors in my anonymous function: | |
// val rtl = books flatMap (b => (toAuthors(b) flatMap toUpperLetters)) | |
// Note: in both cases one would normally not bother with the parenthesis around the body of the anonymous function. | |
// Whichever way we write it the second flatMap has been pulled into the body of the first function. | |
assert(ltr === rtl) | |
} | |
// 3. Do things look better if I just use the expanded out anonymous functions from the start? | |
// Not really... | |
test("flatMap with anonymous functions is associative") { | |
val ltr = (books flatMap (b => b.authors)) flatMap (s => s.toUpperCase.toList) | |
// Well I certainly can't then write... | |
// val rtl = books flatMap ((b => b.authors) flatMap (s => s.toUpperCase.toList)) | |
// Instead as before I have to move the second flatMap into the body of my first function: | |
val rtl = books flatMap (b => (b.authors flatMap (s => s.toUpperCase.toList))) | |
// Again one wouldn't normally bother with the parenthesis around the function body. | |
assert(ltr === rtl) | |
} | |
// Aside: in most cases above s.toUpperCase.toList can be written as just s.toUpperCase as Scala can work out that a | |
// sequence is required and knows how to do an implicit conversion. However I've explicitly invoked toList throughout | |
// to be consistent and clearer. | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment