## Scala for Java Developers ### Lesson 3 ~ Let's explore two more features of Scala ~ ## Option Note: - This was actually needed in the first lesson's exercises, but we didn't cover it in much detail. ~ Scala's answer to null Note: - I'm sure you're all familiar with null... So what do you think of it? ~ > "I call it my billion-dollar mistake . . . I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement."
- Tony Hoare, creator of null
Note: - Tony Hoare, also known for inventing quicksort, added nulls to ALGOL W back in 1965, and isn't a fan. ~ nulls are bad
NullPointerExceptions should not happen Note: - An NPE happens when they you try to access a field on something that is null. - When you see an NPE, really **you're discovering something program**: that in that piece of code, the object may or may not be present. - But unfortunately, **you're finding that out at runtime**! I'd much rather know about it at compile time. ~ Let's walk through a motivating example an Oracle blog post about Optional.
http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html
~ ```java String version = computer.getSoundcard().getUSB().getVersion(); ``` Note: - What could go wrong with that code? - Well, not all computers have a soundcard (e.g. RPi). - Not all soundcards have USB ~ ```java String version = "UNKNOWN"; if(computer != null){ Soundcard soundcard = computer.getSoundcard(); if(soundcard != null){ USB usb = soundcard.getUSB(); if(usb != null){ version = usb.getVersion(); } } } ``` Note: - So this is the safe/defensive version. - Nothing helped me know I should do this. Nothing told me the previous code was unsafe. - If you use the @Nullable annotation on your Javadoc, then maybe your IDE or a linter will tell you? - But the API/types and the compiler didn't direct me to this, I just have to figure it out. - And we can fall into the trap of getting too defensive; how do I know `usb.getVersion()` won't be null? ~ You can use Java 8's Optional instead Note: - So this is largely accepted to be a problem, and in Java 8, Optional was added. - And it existed in Guava before that. ~ ```java String name = computer.flatMap(Computer::getSoundcard) .flatMap(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN"); ``` Note: - If computer was Optional, and the usb getter returned Optional, we could have this. - flatMap is a higher order function. It takes a function which takes the object inside the Optional and returns another Optional. ~ Unfortunately, this was only added in Java 8.
Existing APIs still use null Note: - E.g. System.getenv, or .get on a map or collection. - So you can't take too much advantage of this in Java. - Most existing libraries/API that won't be changed to avoid breaking backwards compatibility, especially the standard library. ~ ```scala sealed trait Option[+A] { def get(): A def map[B](f: A => B): Option[B] def getOrElse[B>:A](default: => B): B def orElse[B>:A](ob: => Option[B]): Option[B] def filter(f: A => Boolean): Option[A] def flatMap[B](f: A => Option[B]): Option[B] } case class Some[+A](get: A) extends Option[A] case object None extends Option[Nothing] ``` Note: - Scala has Option which is very similar to Java's Optional - Here's the simplified signature of Option. - get have .get, .map, .flatMap etc just like Java. - An important point is that this has been in Scala from the beginning. So the standard library and other libraries all heavily use this. - You can rely on it being there in idiomatic Scala code/libraries. You don't have to be defensive. ~ ```scala val name = computer.flatMap(_.getSoundcard) .flatMap(_.getUSB) .map(_.getVersion) .getOrElse("UNKNOWN") ``` Note: - Java's Optional example translates to this in Scala. - But in Scala it's idiomatic to keep it an Option as long as possible. So it would become... ~ ```scala val name = computer.flatMap(_.getSoundcard) .flatMap(_.getUSB) .map(_.getVersion) ``` Note: - Whomever uses `name` has more imformation if we give them an empty Option (AKA None). - If we return UNKNOWN they may end up checking for that ~ But we can rewrite that again: ```scala val name = for { c <- computer sc <- c.getSoundcard usb <- sc.getUSB } yield usb.getVersion ``` Note: - This brings us into another feature in Scala. - This is called a for-comprehension. - You'll notice the `for` keyword. So this kind of looks like a for loop. - But we're not working with a list, so what on earth is going on?! ~~~~ ## For-comprehensions Note: - That last example used a for-comprehension. - Something similar to a for loop, which can also work on options. ~ foreach ```scala val otherTeachers = List("Sonia", "Povilas") for (teacher <- otherTeachers) { println(s"Thanks $teacher for helping teach!") } ``` Note: - Here's a "for-loop" in Scala - Looks similar to Java - But actually, this is syntacic sugar for... ~ foreach ```scala val otherTeachers = List("Sonia", "Povilas") for (teacher <- otherTeachers) { println(s"Thanks $teacher for helping teach!") } otherTeachers.foreach( teacher => println(s"Thanks $teacher for helping teach!") ) ``` Note: - But really, this is a "for-comprehension" - The compiler changes it into foreach; these are completely equivalent ~ map (in Java) ```java List
numbers = ... List
squares = new ArrayList<>(); for (int n : numbers) { squares.add(n ^ 2) } List
squares2 = numbers.stream().map(square) .collect(Collectors.toList()); ``` ~ map (in Scala) ```scala val numbers = 1 to 10 val squares = for (n <- numbers) yield n ^ 2 val squares2 = numbers.map(n => n ^ 2) ``` Note: - Just like if-statements and everything else, for-comprehensions return a value. - With foreach, the point is to have a side effect. So there's nothing to return and `Unit` is returned. - But here, notice the `yield` keyword. - We're going through a list, and returning a new value for each number in the list. - The yield in the for-comprehension gets turned into a .map operation. ~ What else does it do? - filter - flatMap - not just for collections Note: - We just looked at how to use for-comprehensions to in place of `.map` and `.filter` on collections. - But it can actually do more, which I won't get into today, but we'll cover later in the course. - We can do filtering inside a for-comprehension - We can use flatMap, which we saw in the soundcard usb version example earlier. - flatMap, in the context of collections, lets us handle nested loops in a for-comprehension - And interestingly, for-comprehensions work on more than just collections. ~~~~ ### Implementing `findMaxPrice` ~ ```scala def findMaxPrice(prices: List[Int]): Option[Int] ``` ~ Imperative ```scala var max: Int = -1 for (p <- prices) { if (p > max) max = p } if (max == -1) None else Some(max) ``` ~ recursion ``` val current = prices.head val rest = prices.tail findMaxPrice(rest).map(maxOfRest => Math.max(current, maxOfRest) ) //not tail recursive //what if the list is empty ``` ~ recursive (safe and efficient) ```scala def findMaxPrice(prices: List[Int]): Option[Int] = { acc(prices, None) } @tailrec def acc(prices: List[Int], maybeMax: Option[Int]): Option[Int] = { if (prices.isEmpty) maybeMax else acc(prices.tail, getNewMax(prices.head, maybeMax)) } ``` ~ fold ```scala prices.foldLeft(-1)( (maxSoFar, next) => Math.max(maxSoFar, next) ) //or prices.foldLeft(-1)(Math.max(_,_)) //but what if the list is empty? ``` ~ fold (safe) ```scala prices.foldLeft(Option.empty[Int])( (acc, next) => acc .map( maxSoFar => Math.max(maxSoFar, next) ) .orElse(Some(next)) ) ``` ~ What you should really do... ```scala prices.max ``` Note: - Under the hood that's actually using quite an advanced concept called typeclasses. ~~~~ Continue with previous exercises.
Solution checklist:
- no vars - no mutable collections - try using for-comprehension instead of map or filter ~~~~
The lesson is coming to an end...
### Feedback: https://goo.gl/forms/SrEAAs54YqaxEIsu1