Some(slides)
val reasonsToUseNull = None
Friday, July 19, 13
Who am I?
Java (& Scala) Developer at Schantz A/S
Polyglot curious, Coursera junkie
Interested in HCI and Usability
https:...
Oh we wish...
val customer = Customers.findById(1234)
customer.getAccount(FUNSTUFF).getLastInterest.getAmount
Friday, July...
Oh we wish...
val customer = Customers.findById(1234)
customer.getAccount(FUNSTUFF).getLastInterest.getAmount
NullPointers...
Classic solutions (java)
Friday, July 19, 13
Classic solutions (java)
Nested if’s
if(customer != null {
! if(customer.getAccount(FUNSTUFF) != null) {
! ! if(customer.g...
Classic solutions (java)
Nested if’s
if(customer != null {
! if(customer.getAccount(FUNSTUFF) != null) {
! ! if(customer.g...
Classic solutions (java)
Nested if’s
if(customer != null {
! if(customer.getAccount(FUNSTUFF) != null) {
! ! if(customer.g...
Classic solutions (java)
Nested if’s
if(customer != null {
! if(customer.getAccount(FUNSTUFF) != null) {
! ! if(customer.g...
Same in Scala
Friday, July 19, 13
Same in Scala
val customer = Customers.findById(1234)
if (customer != null) {
! val account = customer.account(FUNSTUFF);
...
Same in Scala
val customer = Customers.findById(1234)
if (customer != null) {
! val account = customer.account(FUNSTUFF);
...
Same in Scala
val customer = Customers.findById(1234)
if (customer != null) {
! val account = customer.account(FUNSTUFF);
...
non-existence
Friday, July 19, 13
non-existence
Java
null, null, null, null :-(
Friday, July 19, 13
non-existence
Java
null, null, null, null :-(
Groovy (et al.)
Safe navigation operator
def amount = customer?.account?.int...
non-existence
Java
null, null, null, null :-(
Groovy (et al.)
Safe navigation operator
def amount = customer?.account?.int...
non-existence
Java
null, null, null, null :-(
Groovy (et al.)
Safe navigation operator
def amount = customer?.account?.int...
non-existence
Java
null, null, null, null :-(
Groovy (et al.)
Safe navigation operator
def amount = customer?.account?.int...
non-existence
Java
null, null, null, null :-(
Groovy (et al.)
Safe navigation operator
def amount = customer?.account?.int...
We need something
like:
Container
Empty
container
Important: Same ‘shape’ outside
Friday, July 19, 13
Let me present:
Friday, July 19, 13
Let me present:
Option monad
Friday, July 19, 13
Let me present:
Option monadSHHH
Friday, July 19, 13
Scala’s Option type:
Some(2) None
Friday, July 19, 13
Option - concept
sealed trait Option[A]
case class Some[A](a: A) extends Option[A]
case class None[A] extends Option[A]
Fr...
Advantages
• Values that may or may not exist now
stated in type system
• Clearly shows possible non-existence
• Compiler ...
Option - in RL
sealed abstract class Option[A] extends Product
case class Some[+A](a: A) extends Option[A]
case object Non...
Option - in RL
sealed abstract class Option[A] extends Product {
def isEmpty: Boolean
def get: A
...
}
final case class So...
WAT?
Friday, July 19, 13
Creating Options
Friday, July 19, 13
Creating Options
• Direct: !
val o = Some(3)
//> o : Option[Int] = Some(3)
val n = None
//> n : None.type = None
Friday, J...
BUT NEVER: val aaargh = Some(null)
Creating Options
• Direct: !
val o = Some(3)
//> o : Option[Int] = Some(3)
val n = None...
BUT NEVER: val aaargh = Some(null)
Creating Options
• Direct: !
val o = Some(3)
//> o : Option[Int] = Some(3)
val n = None...
val schroedingersBox : Option[Cat] =
! if(random.nextBoolean) then
Some(Garfield)
else
None
Friday, July 19, 13
Many mays to use
• isDefined
• isEmpty
Friday, July 19, 13
Many mays to use
• isDefined
• isEmpty
if (customer.isDefined)
! customer.account;
Friday, July 19, 13
Many mays to use
• isDefined
• isEmpty
if (customer.isDefined)
! customer.account;
Much more type-safe and null-safe
than o...
get?
three.get
! //> res10: Int = 3
nope.get
! //> java.util.NoSuchElementException: None.get
Friday, July 19, 13
get?
three.get
! //> res10: Int = 3
nope.get
! //> java.util.NoSuchElementException: None.get
$> Yay. We can still write t...
Apprentice level:
Pattern matching
Friday, July 19, 13
Apprentice level:
Pattern matching
val foo = request.param("foo") match
{
! case Some(foo) => foo
! case None => "Default ...
Apprentice level:
Pattern matching
val foo = request.param("foo") match
{
! case Some(foo) => foo
! case None => "Default ...
Apprentice level:
Pattern matching
val foo = request.param("foo") match
{
! case Some(foo) => foo
! case None => "Default ...
What we really want is
Friday, July 19, 13
What we really want is
... to do stuff with our values
Friday, July 19, 13
What we really want is
... to do stuff with our values
Friday, July 19, 13
But...
Friday, July 19, 13
We want...?
Friday, July 19, 13
Padawan level:
functional
• Treat Option as a (very small) collection
• “Biased” towards Some
• map, flatMap etc.
• and com...
map
Friday, July 19, 13
map
! val three = Some(3)
! ! ! ! > three : Option[Int] = Some(3)
Friday, July 19, 13
map
! val three = Some(3)
! ! ! ! > three : Option[Int] = Some(3)
! val res = three.map(_ + 3)
Friday, July 19, 13
map
! val three = Some(3)
! ! ! ! > three : Option[Int] = Some(3)
! val res = three.map(_ + 3)
> res: Option[Int] = Some(6...
map
option.map(foo(_))
equivalent to:
option match {
case None => None
case Some(x) => Some(foo(x))
}
Friday, July 19, 13
Examples
Friday, July 19, 13
def sqr(i:Int) = {i*i}
Examples
Friday, July 19, 13
def sqr(i:Int) = {i*i}
val three = Option(3)
Examples
Friday, July 19, 13
def sqr(i:Int) = {i*i}
val three = Option(3)
three.map(i => sqr(i))
Examples
Friday, July 19, 13
def sqr(i:Int) = {i*i}
val three = Option(3)
three.map(i => sqr(i))
//> res4: Option[Int] = Some(9)
Examples
Friday, July ...
def sqr(i:Int) = {i*i}
val three = Option(3)
three.map(i => sqr(i))
//> res4: Option[Int] = Some(9)
three.map(sqr(_))
Exam...
def sqr(i:Int) = {i*i}
val three = Option(3)
three.map(i => sqr(i))
//> res4: Option[Int] = Some(9)
three.map(sqr(_))
//> ...
def sqr(i:Int) = {i*i}
val three = Option(3)
three.map(i => sqr(i))
//> res4: Option[Int] = Some(9)
three.map(sqr(_))
//> ...
def sqr(i:Int) = {i*i}
val three = Option(3)
three.map(i => sqr(i))
//> res4: Option[Int] = Some(9)
three.map(sqr(_))
//> ...
flatMap
option.flatMap(foo(_))
is equivalent to:
option match {
case None => None
case Some(x) => foo(x)
}
Friday, July 19,...
three.flatMap(x => Some(x.toString))
Option[java.lang.String] = Some(3)
nah.flatMap(x => Some(x.toString))
Option[java.lan...
Side effects:
foreach
option.foreach(foo(_))
is equivalent to:
option match {
case None => {}
case Some(x) => foo(x)
}
Fri...
three.foreach(println(_))
Friday, July 19, 13
val userOpt = UserDao.findById(userId)
userOpt.foreach(user => println(user.name))
or, even shorter:
userOpt.foreach(print...
Working with lists
val o1 = Option(1)! ! //> o1 : Option[Int] = Some(1)
val o2 = Option(2) //> o2 : Option[Int] = Some(2)
...
Jedi level:
for comprehesions
val ageOpt = for {
! user <- UserDao.findById(userId)
! age <- user.ageOpt
} yield age
Frida...
Jedi mind tricks
//we have a ‘User’ with mandatory name, but optional age
case class User(val name:String , val age:Option...
val userOpt =
UserDao.findById(userId) OrElse Some(UserDao.create)
or:
val user =
UserDao.findById(userId) getOrElse UserD...
other option options
def filter(p: A => Boolean): Option[A]
def exists(p: A => Boolean): Boolean
fold
collect
iterator
toL...
Resources
References, Thanks, Resources and further
reading
Attributions:
Thanks to Adit Bhargava for a great blogpost on ...
Upcoming SlideShare
Loading in...5
×

Introduction to Option monad in Scala

2,272

Published on

Presentation on handling non-existence of data in Java et. al. (e.g. the problem with pesky nulls) and an introduction to the Option monad in Scala as a "solution" to this problem.

I presented this talk June, 28th 2013 at CPH Scala Group meeting, and a week later, July 3rd, at the "Scala User Group Århus" meetup.

In this short introduction, I try to frame the problem, i.e. the large amounts of error-prone null-checking code we usually have to write in Java, and Introduce the Option monad (Some/None) in Scala, as a solution. I explain the basics of what the Option class provides, and various ways of using it, ranging from basic level isEmtpy, over pattern-matching to more advanced fully functional "collection-style" (e.g. map, flatMap) operations and finally by using the for-comprehension.
Also includes links to relevant resources for further reading on the last slide.

Transcript of "Introduction to Option monad in Scala"

  1. 1. Some(slides) val reasonsToUseNull = None Friday, July 19, 13
  2. 2. Who am I? Java (& Scala) Developer at Schantz A/S Polyglot curious, Coursera junkie Interested in HCI and Usability https://github.com/JKrag @jankrag • Geek, builder and flyer of kites, reptile & cat breeder, Rubik's puzzle fan Friday, July 19, 13
  3. 3. Oh we wish... val customer = Customers.findById(1234) customer.getAccount(FUNSTUFF).getLastInterest.getAmount Friday, July 19, 13
  4. 4. Oh we wish... val customer = Customers.findById(1234) customer.getAccount(FUNSTUFF).getLastInterest.getAmount NullPointers ! Friday, July 19, 13
  5. 5. Classic solutions (java) Friday, July 19, 13
  6. 6. Classic solutions (java) Nested if’s if(customer != null { ! if(customer.getAccount(FUNSTUFF) != null) { ! ! if(customer.getAccount(FUNSTUFF).getLastInterest != null) { ! ! ! return customer.getAccount(FUNSTUFF).getLastInterest.getAmount ! ! } ! } } return null; Friday, July 19, 13
  7. 7. Classic solutions (java) Nested if’s if(customer != null { ! if(customer.getAccount(FUNSTUFF) != null) { ! ! if(customer.getAccount(FUNSTUFF).getLastInterest != null) { ! ! ! return customer.getAccount(FUNSTUFF).getLastInterest.getAmount ! ! } ! } } return null; UGLY Friday, July 19, 13
  8. 8. Classic solutions (java) Nested if’s if(customer != null { ! if(customer.getAccount(FUNSTUFF) != null) { ! ! if(customer.getAccount(FUNSTUFF).getLastInterest != null) { ! ! ! return customer.getAccount(FUNSTUFF).getLastInterest.getAmount ! ! } ! } } return null; Early returns if (customer == null) return null; if (customer.getAccount(FUNSTUFF) == null) return null; if (customer.getAccount(FUNSTUFF).getLastInterest == null) return null; return customer.getAccount(FUNSTUFF).getLastInterest.getAmount UGLY Friday, July 19, 13
  9. 9. Classic solutions (java) Nested if’s if(customer != null { ! if(customer.getAccount(FUNSTUFF) != null) { ! ! if(customer.getAccount(FUNSTUFF).getLastInterest != null) { ! ! ! return customer.getAccount(FUNSTUFF).getLastInterest.getAmount ! ! } ! } } return null; Early returns if (customer == null) return null; if (customer.getAccount(FUNSTUFF) == null) return null; if (customer.getAccount(FUNSTUFF).getLastInterest == null) return null; return customer.getAccount(FUNSTUFF).getLastInterest.getAmount UGLY Still UGLY! Friday, July 19, 13
  10. 10. Same in Scala Friday, July 19, 13
  11. 11. Same in Scala val customer = Customers.findById(1234) if (customer != null) { ! val account = customer.account(FUNSTUFF); ! if (account != null) { ! ! val interest = account.getLastInterest ! ! if (interest != null) ! ! ! interest.amount ! ! else ! ! ! null ! } else ! ! null } else ! null Friday, July 19, 13
  12. 12. Same in Scala val customer = Customers.findById(1234) if (customer != null) { ! val account = customer.account(FUNSTUFF); ! if (account != null) { ! ! val interest = account.getLastInterest ! ! if (interest != null) ! ! ! interest.amount ! ! else ! ! ! null ! } else ! ! null } else ! null Even in Scala, Still UGLY! Friday, July 19, 13
  13. 13. Same in Scala val customer = Customers.findById(1234) if (customer != null) { ! val account = customer.account(FUNSTUFF); ! if (account != null) { ! ! val interest = account.getLastInterest ! ! if (interest != null) ! ! ! interest.amount ! ! else ! ! ! null ! } else ! ! null } else ! null Even in Scala, Still UGLY! ...and errorprone Friday, July 19, 13
  14. 14. non-existence Friday, July 19, 13
  15. 15. non-existence Java null, null, null, null :-( Friday, July 19, 13
  16. 16. non-existence Java null, null, null, null :-( Groovy (et al.) Safe navigation operator def amount = customer?.account?.interest?.amount Friday, July 19, 13
  17. 17. non-existence Java null, null, null, null :-( Groovy (et al.) Safe navigation operator def amount = customer?.account?.interest?.amount Ceylon, Kotlin etc. both nullable and null-safe types... String name = null; //compile error: null is not an instance of String String? name = null; //OK Friday, July 19, 13
  18. 18. non-existence Java null, null, null, null :-( Groovy (et al.) Safe navigation operator def amount = customer?.account?.interest?.amount Ceylon, Kotlin etc. both nullable and null-safe types... String name = null; //compile error: null is not an instance of String String? name = null; //OK Others (e.g. Clojure): ‘nil’ type - close but...! Friday, July 19, 13
  19. 19. non-existence Java null, null, null, null :-( Groovy (et al.) Safe navigation operator def amount = customer?.account?.interest?.amount Ceylon, Kotlin etc. both nullable and null-safe types... String name = null; //compile error: null is not an instance of String String? name = null; //OK Others (e.g. Clojure): ‘nil’ type - close but...! Scala .... Friday, July 19, 13
  20. 20. non-existence Java null, null, null, null :-( Groovy (et al.) Safe navigation operator def amount = customer?.account?.interest?.amount Ceylon, Kotlin etc. both nullable and null-safe types... String name = null; //compile error: null is not an instance of String String? name = null; //OK Others (e.g. Clojure): ‘nil’ type - close but...! Scala .... patience... Friday, July 19, 13
  21. 21. We need something like: Container Empty container Important: Same ‘shape’ outside Friday, July 19, 13
  22. 22. Let me present: Friday, July 19, 13
  23. 23. Let me present: Option monad Friday, July 19, 13
  24. 24. Let me present: Option monadSHHH Friday, July 19, 13
  25. 25. Scala’s Option type: Some(2) None Friday, July 19, 13
  26. 26. Option - concept sealed trait Option[A] case class Some[A](a: A) extends Option[A] case class None[A] extends Option[A] Friday, July 19, 13
  27. 27. Advantages • Values that may or may not exist now stated in type system • Clearly shows possible non-existence • Compiler forces you to deal with it • You won’t accidentally rely on value Friday, July 19, 13
  28. 28. Option - in RL sealed abstract class Option[A] extends Product case class Some[+A](a: A) extends Option[A] case object None extends Option[Nothing] Friday, July 19, 13
  29. 29. Option - in RL sealed abstract class Option[A] extends Product { def isEmpty: Boolean def get: A ... } final case class Some[+A](x: A) extends Option[A] { def isEmpty = false def get = x } case object None extends Option[Nothing] { def isEmpty = true def get = throw new NoSuchElementException("None.get") } Friday, July 19, 13
  30. 30. WAT? Friday, July 19, 13
  31. 31. Creating Options Friday, July 19, 13
  32. 32. Creating Options • Direct: ! val o = Some(3) //> o : Option[Int] = Some(3) val n = None //> n : None.type = None Friday, July 19, 13
  33. 33. BUT NEVER: val aaargh = Some(null) Creating Options • Direct: ! val o = Some(3) //> o : Option[Int] = Some(3) val n = None //> n : None.type = None Friday, July 19, 13
  34. 34. BUT NEVER: val aaargh = Some(null) Creating Options • Direct: ! val o = Some(3) //> o : Option[Int] = Some(3) val n = None //> n : None.type = None • Factory method on companion object: ! val o = Option(3) //> o : Option[Int] = Some(3) val nn = Option(null) //> nn : Option[Null] = None Friday, July 19, 13
  35. 35. val schroedingersBox : Option[Cat] = ! if(random.nextBoolean) then Some(Garfield) else None Friday, July 19, 13
  36. 36. Many mays to use • isDefined • isEmpty Friday, July 19, 13
  37. 37. Many mays to use • isDefined • isEmpty if (customer.isDefined) ! customer.account; Friday, July 19, 13
  38. 38. Many mays to use • isDefined • isEmpty if (customer.isDefined) ! customer.account; Much more type-safe and null-safe than original null-based java-flavour, but code just as ugly Friday, July 19, 13
  39. 39. get? three.get ! //> res10: Int = 3 nope.get ! //> java.util.NoSuchElementException: None.get Friday, July 19, 13
  40. 40. get? three.get ! //> res10: Int = 3 nope.get ! //> java.util.NoSuchElementException: None.get $> Yay. We can still write the other ugly version with Exception handling :-) Friday, July 19, 13
  41. 41. Apprentice level: Pattern matching Friday, July 19, 13
  42. 42. Apprentice level: Pattern matching val foo = request.param("foo") match { ! case Some(foo) => foo ! case None => "Default foo" } Friday, July 19, 13
  43. 43. Apprentice level: Pattern matching val foo = request.param("foo") match { ! case Some(foo) => foo ! case None => "Default foo" } Sometimes useful, but... Friday, July 19, 13
  44. 44. Apprentice level: Pattern matching val foo = request.param("foo") match { ! case Some(foo) => foo ! case None => "Default foo" } Sometimes useful, but... at some point a Jedi you must become Friday, July 19, 13
  45. 45. What we really want is Friday, July 19, 13
  46. 46. What we really want is ... to do stuff with our values Friday, July 19, 13
  47. 47. What we really want is ... to do stuff with our values Friday, July 19, 13
  48. 48. But... Friday, July 19, 13
  49. 49. We want...? Friday, July 19, 13
  50. 50. Padawan level: functional • Treat Option as a (very small) collection • “Biased” towards Some • map, flatMap etc. • and compose to your desire when the option contains a value Friday, July 19, 13
  51. 51. map Friday, July 19, 13
  52. 52. map ! val three = Some(3) ! ! ! ! > three : Option[Int] = Some(3) Friday, July 19, 13
  53. 53. map ! val three = Some(3) ! ! ! ! > three : Option[Int] = Some(3) ! val res = three.map(_ + 3) Friday, July 19, 13
  54. 54. map ! val three = Some(3) ! ! ! ! > three : Option[Int] = Some(3) ! val res = three.map(_ + 3) > res: Option[Int] = Some(6) Friday, July 19, 13
  55. 55. map option.map(foo(_)) equivalent to: option match { case None => None case Some(x) => Some(foo(x)) } Friday, July 19, 13
  56. 56. Examples Friday, July 19, 13
  57. 57. def sqr(i:Int) = {i*i} Examples Friday, July 19, 13
  58. 58. def sqr(i:Int) = {i*i} val three = Option(3) Examples Friday, July 19, 13
  59. 59. def sqr(i:Int) = {i*i} val three = Option(3) three.map(i => sqr(i)) Examples Friday, July 19, 13
  60. 60. def sqr(i:Int) = {i*i} val three = Option(3) three.map(i => sqr(i)) //> res4: Option[Int] = Some(9) Examples Friday, July 19, 13
  61. 61. def sqr(i:Int) = {i*i} val three = Option(3) three.map(i => sqr(i)) //> res4: Option[Int] = Some(9) three.map(sqr(_)) Examples Friday, July 19, 13
  62. 62. def sqr(i:Int) = {i*i} val three = Option(3) three.map(i => sqr(i)) //> res4: Option[Int] = Some(9) three.map(sqr(_)) //> res5: Option[Int] = Some(9) Examples Friday, July 19, 13
  63. 63. def sqr(i:Int) = {i*i} val three = Option(3) three.map(i => sqr(i)) //> res4: Option[Int] = Some(9) three.map(sqr(_)) //> res5: Option[Int] = Some(9) three.map(sqr) Examples Friday, July 19, 13
  64. 64. def sqr(i:Int) = {i*i} val three = Option(3) three.map(i => sqr(i)) //> res4: Option[Int] = Some(9) three.map(sqr(_)) //> res5: Option[Int] = Some(9) three.map(sqr) //> res6: Option[Int] = Some(9) Examples Friday, July 19, 13
  65. 65. flatMap option.flatMap(foo(_)) is equivalent to: option match { case None => None case Some(x) => foo(x) } Friday, July 19, 13
  66. 66. three.flatMap(x => Some(x.toString)) Option[java.lang.String] = Some(3) nah.flatMap(x => Some(x.toString)) Option[java.lang.String] = None Friday, July 19, 13
  67. 67. Side effects: foreach option.foreach(foo(_)) is equivalent to: option match { case None => {} case Some(x) => foo(x) } Friday, July 19, 13
  68. 68. three.foreach(println(_)) Friday, July 19, 13
  69. 69. val userOpt = UserDao.findById(userId) userOpt.foreach(user => println(user.name)) or, even shorter: userOpt.foreach(println) Friday, July 19, 13
  70. 70. Working with lists val o1 = Option(1)! ! //> o1 : Option[Int] = Some(1) val o2 = Option(2) //> o2 : Option[Int] = Some(2) val o3 = Option(3) //> o3 : Option[Int] = Some(3) val l = List(o1, nope, o2, nah, o3) ! //> l : List[Option[Int]] = List(Some(1), None, Some(2), None, Some(3)) ! l.map(_.map(sqr)) ! ! //> res8: List[Option[Int]] = List(Some(1), None, Some(4), None, Some(9)) l.flatMap(_.map(sqr)) ! ! //> res9: List[Int] = List(1, 4, 9) Friday, July 19, 13
  71. 71. Jedi level: for comprehesions val ageOpt = for { ! user <- UserDao.findById(userId) ! age <- user.ageOpt } yield age Friday, July 19, 13
  72. 72. Jedi mind tricks //we have a ‘User’ with mandatory name, but optional age case class User(val name:String , val age:Option[Int]) def prettyPrint(user: User) = ! List(Option(user.name), user.age).flatten.mkString(", ") val foo = User("Foo", Some(42)) val bar = User("Bar", None) prettyPrint(foo) //prints "Foo, 42" prettyPrint(bar) //prints "Bar" Friday, July 19, 13
  73. 73. val userOpt = UserDao.findById(userId) OrElse Some(UserDao.create) or: val user = UserDao.findById(userId) getOrElse UserDao.create Friday, July 19, 13
  74. 74. other option options def filter(p: A => Boolean): Option[A] def exists(p: A => Boolean): Boolean fold collect iterator toList Friday, July 19, 13
  75. 75. Resources References, Thanks, Resources and further reading Attributions: Thanks to Adit Bhargava for a great blogpost on monads in Haskel and for letting me use his cartoon drawings: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html For broadening my mind on higher-order use of Options: http://blog.tmorris.net/posts/ scalaoption-cheat-sheet/ Further reading http://marakana.com/static/courseware/scala/presentation/comprehending-monads.html http://blog.xebia.com/2011/06/02/scala-options-the-slick-way/ Friday, July 19, 13
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×