Skip to content

Instantly share code, notes, and snippets.

@george-hawkins
Last active August 18, 2016 22:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save george-hawkins/6b2f7034f79c244ad232b1f20691438f to your computer and use it in GitHub Desktop.
Save george-hawkins/6b2f7034f79c244ad232b1f20691438f to your computer and use it in GitHub Desktop.
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