Created
June 5, 2014 22:52
-
-
Save zipcode/95e9e96a864c210027db to your computer and use it in GitHub Desktop.
A shitty explanation of Monads in Scala
This file contains hidden or 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
import scalaz._ | |
import Scalaz._ | |
object HowMonadsWork { | |
/* | |
You may have found yourself asking, what is a Monad, anyway? | |
Some kinda burrito-spacesuit-magical-wavy thing, if you ask the internet. | |
The internet isn't very helpful. This probably isn't, either. | |
"Monad" is an interface which some data structures implement. That's all. | |
What, exacly, does this interface specify? | |
1. It implements "Functor" | |
2. It specifies a join operation | |
Oh, very helpful. What does a Functor interface specify? | |
1. It implements map | |
2. You can put a value in it | |
Cool! | |
Anything you can put a value in and map over is a Functor. | |
*/ | |
def functorExample() { | |
/* You can map an Option */ | |
val a = Some(10) | |
val a1 = a map { _*2 } | |
assert(a.get*2 == a1.get) | |
/* You can map a List */ | |
val l = List(1, 2) | |
val l1 = l map { _*2 } | |
assert(l1 == List(2, 4)) | |
/* You can map a function */ | |
val f = { (_: Int) * 2 } | |
val f1 = f map { _+1 } | |
assert(f1(1) == 3) | |
} | |
/* | |
With me so far? Cool! | |
Onwards, to the Monad interface. | |
'join' means there's some semantics so that you can turn | |
Monad[Monad[T]] to Monad[T] for some types Monad and T. | |
In lists, for example, that means flattening them one level. | |
join is added by scalaz using implicit magic, if you're wondering. | |
*/ | |
def joinExample() { | |
val a: List[List[Int]] = List(List(1, 2), List(3, 4)) | |
assert(a.join == List(1, 2, 3, 4)) | |
val b: Option[Option[Int]] = Some(Some(3)) | |
assert(b.join == Some(3)) | |
} | |
/* | |
But usually, we don't actually pay attention to join. | |
The canonical monad operation is a combination of map and | |
join, usually known as bind, >>=, or flatMap. | |
"flatMap" is the most descriptive name for it, and realising | |
that flatMap *was* bind was one of the critical insights for | |
understanding this for me, after reading too many Haskell guides. | |
Anyway, you'll find that flatMap is on most collection-y things | |
in Scala. | |
*/ | |
def flatMapExample() { | |
val a = List(1, 2, 3) | |
def f(x: Int) = List(x, 100+x) | |
assert(a.flatMap(f) == List(1, 101, 2, 102, 3, 103)) | |
val b = Some(0) | |
val b1 = b flatMap { x => if (x == 0) None else Some(1/x) } | |
assert(b1 == None) | |
} | |
/* | |
And finally, 'for' notation, also known as 'do' notation in Haskell. | |
'for' is simply syntactic suger for map and flatmap. Anything before | |
the yield is flatMapped, and the yield is a final map. | |
*/ | |
def forExample() { // pfft | |
val a = List(1, 2, 3) | |
val a1 = for ( | |
v <- a; | |
x <- List(v, v+100) | |
) yield x | |
val a2 = a >>= { v => List(v, v+100) } | |
assert(a1 == List(1, 101, 2, 102, 3, 103)) | |
assert(a1 == a2) | |
val b = Option(2) | |
val b1 = for ( | |
x <- b; // for each x in b (ie, 2) | |
x1 <- if (x==0) None else Some(1/x) // Give us None if that was 0, otherwise 1/x | |
) yield x1 // And output that | |
assert(b1 == Option(1/2)) | |
/* This might be surprising if you're used to this! */ | |
for (x <- 0 to 5) yield x*2 | |
/* But it's just sugar again */ | |
(0 to 5) map { _*2 } | |
/* So of course this */ | |
val f1 = for (x <- 0 to 2; y <- 0 to 2) yield (x, y) | |
/* is actually */ | |
val f2 = (0 to 2) flatMap { x => (0 to 2) map { y => (x, y)}} | |
assert(f1 == f2) | |
} | |
def main(args: Array[String]) { | |
functorExample() | |
joinExample() | |
flatMapExample() | |
forExample() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment