Introduction to Option monad in Scala

  • 1,227 views
Uploaded 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. …

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.

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
1,227
On Slideshare
0
From Embeds
0
Number of Embeds
2

Actions

Shares
Downloads
16
Comments
0
Likes
7

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Some(slides) val reasonsToUseNull = None Friday, July 19, 13
  • 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. Oh we wish... val customer = Customers.findById(1234) customer.getAccount(FUNSTUFF).getLastInterest.getAmount Friday, July 19, 13
  • 4. Oh we wish... val customer = Customers.findById(1234) customer.getAccount(FUNSTUFF).getLastInterest.getAmount NullPointers ! Friday, July 19, 13
  • 5. Classic solutions (java) Friday, July 19, 13
  • 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. 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. 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. 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. Same in Scala Friday, July 19, 13
  • 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. 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. 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. non-existence Friday, July 19, 13
  • 15. non-existence Java null, null, null, null :-( Friday, July 19, 13
  • 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. 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. 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. 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. 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. We need something like: Container Empty container Important: Same ‘shape’ outside Friday, July 19, 13
  • 22. Let me present: Friday, July 19, 13
  • 23. Let me present: Option monad Friday, July 19, 13
  • 24. Let me present: Option monadSHHH Friday, July 19, 13
  • 25. Scala’s Option type: Some(2) None Friday, July 19, 13
  • 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. 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. 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. 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. WAT? Friday, July 19, 13
  • 31. Creating Options Friday, July 19, 13
  • 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. 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. 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. val schroedingersBox : Option[Cat] = ! if(random.nextBoolean) then Some(Garfield) else None Friday, July 19, 13
  • 36. Many mays to use • isDefined • isEmpty Friday, July 19, 13
  • 37. Many mays to use • isDefined • isEmpty if (customer.isDefined) ! customer.account; Friday, July 19, 13
  • 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. get? three.get ! //> res10: Int = 3 nope.get ! //> java.util.NoSuchElementException: None.get Friday, July 19, 13
  • 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. Apprentice level: Pattern matching Friday, July 19, 13
  • 42. Apprentice level: Pattern matching val foo = request.param("foo") match { ! case Some(foo) => foo ! case None => "Default foo" } Friday, July 19, 13
  • 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. 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. What we really want is Friday, July 19, 13
  • 46. What we really want is ... to do stuff with our values Friday, July 19, 13
  • 47. What we really want is ... to do stuff with our values Friday, July 19, 13
  • 48. But... Friday, July 19, 13
  • 49. We want...? Friday, July 19, 13
  • 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. map Friday, July 19, 13
  • 52. map ! val three = Some(3) ! ! ! ! > three : Option[Int] = Some(3) Friday, July 19, 13
  • 53. map ! val three = Some(3) ! ! ! ! > three : Option[Int] = Some(3) ! val res = three.map(_ + 3) Friday, July 19, 13
  • 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. map option.map(foo(_)) equivalent to: option match { case None => None case Some(x) => Some(foo(x)) } Friday, July 19, 13
  • 56. Examples Friday, July 19, 13
  • 57. def sqr(i:Int) = {i*i} Examples Friday, July 19, 13
  • 58. def sqr(i:Int) = {i*i} val three = Option(3) Examples Friday, July 19, 13
  • 59. def sqr(i:Int) = {i*i} val three = Option(3) three.map(i => sqr(i)) Examples Friday, July 19, 13
  • 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. 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. 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. 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. 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. flatMap option.flatMap(foo(_)) is equivalent to: option match { case None => None case Some(x) => foo(x) } Friday, July 19, 13
  • 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. Side effects: foreach option.foreach(foo(_)) is equivalent to: option match { case None => {} case Some(x) => foo(x) } Friday, July 19, 13
  • 68. three.foreach(println(_)) Friday, July 19, 13
  • 69. val userOpt = UserDao.findById(userId) userOpt.foreach(user => println(user.name)) or, even shorter: userOpt.foreach(println) Friday, July 19, 13
  • 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. Jedi level: for comprehesions val ageOpt = for { ! user <- UserDao.findById(userId) ! age <- user.ageOpt } yield age Friday, July 19, 13
  • 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. val userOpt = UserDao.findById(userId) OrElse Some(UserDao.create) or: val user = UserDao.findById(userId) getOrElse UserDao.create Friday, July 19, 13
  • 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. 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