Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A Scala Map

Ben Lever
August 13, 2014

A Scala Map

Many people who are just starting out with Scala are often also interested in learning to program functionally. This is quite the challenging proposition: functional programming in itself is a learning curve not to be underestimated, and this is not aided by the confusion of where to start given the breadth of the Scala language.

In this introductory talk I will present some suggestions, and drop some signposts for where a newcomer to Scala should invest their efforts. In some ways, this will hopefully be a talk that I would have wanted to have seen when I first started using Scala.

Originally presented at ScalaSyd - http://www.meetup.com/scalasyd/events/194624612/

Ben Lever

August 13, 2014
Tweet

Other Decks in Programming

Transcript

  1. What this talk is not about • Why you should

    use Scala • Burritos • Astronauts • Haskell Hi, I’m Scippy. ! I’m here to give you some pointers! ! Don’t ask me questions about these sort of things.
  2. What this talk is about • Functional programming with Scala

    • Highlighting concepts that may be new • Concepts you should care about and invest in
  3. Did you know Option is an Algebraic Data Type (ADT)?

    Option 1 sealed trait Option[A] { ... } 2 case class Some[A](x: A) extends Option[A] 3 case object None extends Option[Nothing] 4 5 6 val someString: Option[String] = Some("hello world") 7 val noString: Option[String] = None 8 9 val someInt: Option[Int] = Some(46) 10 val noInt: Option[Int] = None 11 12 13 Some(36).get // 36 14 None.get // exception!!! 15 16 Some(36).getOrElse(-1) // 36 17 None.getOrElse(-1) // -1
  4. ADTs 1 // Sum-type 2 sealed trait TrafficLight 3 case

    object GreenLight extends TrafficLight 4 case object AmberLight extends TrafficLight 5 case object RedLight extends TrafficLight 6 7 8 // Product-type 9 case class Person(name: String, age: Int) 10 11 12 // Recursive sum-type 13 sealed trait Tree[A] 14 case class Branch(left: Tree[A], right: Tree[A]) extends Tree[A] 15 case object Leaf(x: A) extends Tree[A]
  5. 1 sealed trait Option[A] { 2 def map[B](f: A =>

    B): Option[B] 3 def flatMap[B](f: A => Option[B]): Option[B] 4 ... 5 } 6 7 8 def f(someInt: Option[Int], i: Int): Option[Int] = 9 someInt.map(x => x + i) 10 11 f(Some(42), 1) // Some(43) 12 f(None, 1) // None 13 14 15 def g(someInt: Option[Int], otherInt: Option[Int]): Option[Int] = 16 someInt.flatMap(x => f(otherInt, x)) 17 18 g(Some(42), Some(3)) // Some(46) 19 g(Some(42), None) // None 20 g(None, Some(3)) // None 21 g(None, None) // None
  6. for-comprehensions 1 // Using a for-comprehension 2 def g(someInt: Option[Int],

    otherInt: Option[Int]): Option[Int] = 3 for { 4 x <- someInt 5 r <- f(otherInt, x) 6 } yield r 7 8 9 g(Some(42), Some(3)) // Some(45) 10 g(Some(42), None) // None 11 g(None, Some(3)) // None 12 g(None, None) // None
  7. 1 // Fictitious Stackoverflow Scala API: 2 def getQuestionOwner(quesId: String):

    Option[String] 3 4 def getUserReputation(userId: String): Option[Int] 5 6 7 // e.g. 8 getUserReputation("miles-sabin") // Some(13319) 9 getUserReputation("afvoip2w") // None 10 11 12 // The reputation of the user who asked a question. 13 def questionReputation(qId: String): Option[Int] = for { 14 qUserId <- getQuestionOwner(qId) 15 qUserRep <- getUserReputation(qUserId) 16 } yield qUserRep 17 18 19 // e.g. 20 questionReputation("7267760") // Some(149620) 21 questionReputation("blahblah") // None Have you looked at Option combinators in Scalaz?
  8. Scalaz • Scala library • Purely functional data structures to

    complement standard library • “Learning Scalaz" 1 1 import scalaz._ 2 import Scalaz._
  9. Have you considered using disjunction? 1 // Constructor conveniences 2

    45.some // Option[Int] 3 "hello".some // Option[String] 4 none[Double] // Option[Double] 5 6 // Syntax 7 45.some | 3 // 45 (alias for getOrElse) 8 none[String] | "empty" // "empty" 9 10 11 // Folding 12 def cata[A, X](oa: Option[A])(some: (A) => X, none: => X): X 13 14 15 // e.g. print the reputation for a Stackoverflow user 16 def printReputation(userId: String) { 17 val oRep: Option[Int] = getUserReputation(userId) 18 val str = oRep.cata( 19 r => s"Reputation for '$userId' is '$r'", 20 s"Could not find '$userId'" 21 ) 22 println(str) 23 }
  10. Disjunction • Defined in Scalaz • Similar to the standard

    library’s Either type • Like Option but allows us to attach a value to a possible failure • Right-biased map and flatMap 1 sealed trait \/[A, B] { ... } 2 case class -\/[A, B](left: A) extends (A \/ B) 3 case class \/-[A, B](right: B) extends (A \/ B)
  11. 1 // Convenience constructors 2 3.right[String] // String \/ Int

    3 "error".left[Int] // String \/ Int 4 5 // Slightly different fictitious Stackoverflow API: 6 def getUserReputation(userId: String): String \/ Int 7 8 // e.g. 9 getUserReputation("miles-sabin") // \/-(13319) 10 getUserReputation("afvoip2w") // -\/(Unknown user 'afvoip2w') 11 12 // map + flatMap are defined for the right side 13 def isAwesome(userId: String): String \/ Boolean = 14 getUserReputation(userId).map(_ > 10000) 15 16 isAwesome("miles-sabin") // \/-(true) 17 isAwesome("afvoip2w") // -\/(Unknown user 'afvoip2w')
  12. 1 import scalaz._, Scalaz._ 2 import argonaut._, Argonaut._ 3 4

    val json = """ 5 { "user" : "miles-sabin", "reputation" : 13319 } 6 """ 7 8 // Parse getting either error message or json 9 val result: String \/ Json = 10 Parse.parse(json) 11 12 // Parse ignoring error messages 13 val option: Option[Json] = 14 Parse.parseOption(json)
  13. Summing up 1 val team = List( 2 "miles-sabin", 3

    "brian-mckenna", 4 "bean-factory", 5 "jed-wesley-smith") 6 7 // List(Some(13319), Some(10754), None, Some(3094)) 8 val reputations: List[Option[Int]] = team.map(getUserReputation) 9 10 val teamReputation: Int = ???
  14. Abstracting Adding Ints Combining Options Accumulating over a List Ah

    … typeclass? 1 def specialSum(xs: List[Option[Int]]): Option[Int] = { 2 3 def go(acc: Option[Int], rem: List[Option[Int]]): Option[Int] = 4 rem match { 5 case y :: ys => (acc, y) match { 6 case (Some(a), Some(i)) => go(Some(a + i), ys) 7 case (Some(a), None) => go(Some(a), ys) 8 case (None, Some(i)) => go(Some(i), ys) 9 case (None, None) => go(None, ys) 10 } 11 case Nil => acc 12 } 13 14 go(none[Int], xs) 15 } 16 17 // e.g. 18 val teamReputation: Int = 19 specialSum(reputations).getOrElse(0) // 27167
  15. Typeclasses • Ad-hoc polymorphism • Decouple behavioural constraints from the

    types that can have the behaviour • Originally from Haskell • A big part of Scalaz (and friends) is typeclasses
  16. 1 // Type-class for combining values of the same type

    2 trait Combine[A] { 3 def combine(a1: A, a2: => A): A 4 def start: A 5 } 6 7 // Type-class instance for Int 8 object IntCombine extends Combine[Int] { 9 def combine(a1: Int, a2: Int): Int = a1 + a2 10 val start: Int = 0 11 } 12 13 def specialSum[A](xs: List[Option[A]])(C: Combine[A]): Option[A] = { 14 15 def go(acc: Option[A], rem: List[Option[A]]): Option[A] = 16 rem match { 17 case y :: ys => (acc, y) match { 18 case (Some(a), Some(i)) => go(Some(C.combine(a, i)), ys) 19 case (Some(a), None) => go(Some(a), ys) 20 case (None, Some(i)) => go(Some(i), ys) 21 case (None, None) => go(None, ys) 22 } 23 case Nil => acc 24 } 25 26 go(none[A], xs) 27 } 28 29 // e.g. 30 val teamReputation: Int = 31 specialSum(reputations)(IntCombine).getOrElse(0) // 27167
  17. 1 // Type-class for combining values of the same type

    2 trait Combine[A] { 3 def combine(a1: A, a2: => A): A 4 def start: A 5 } 6 7 // Type class instance for Option with constraint 8 def OptionCombine[A](C: Combine[A]) extends Combine[Option[A]] { 9 def combine(a1: Option[A], a2: Option[A]) = (a1, a2) match { 10 case (Some(x), Some(y)) => Some(C.combine(x, y)) 11 case (Some(x), None) => Some(x) 12 case (None, Some(y)) => Some(y) 13 case (None, None) => None 14 } 15 val start: Option[A] = None 16 } 17 18 def specialSum[A](xs: List[A])(C: Combine[A]): A = { 19 def go(acc: A, rem: List[A]): A = rem match { 20 case y :: ys => go(C.combine(acc, y), ys) 21 case Nil => acc 22 } 23 go(C.start, xs) 24 } 25 26 // e.g. 27 val teamReputation: Int = 28 specialSum(reputations)(OptionCombine(IntCombine)).getOrElse(0)
  18. Implicits? Context bounds? 1 // Type-class for structures that can

    be accumulated over 2 trait AccumulateOver[L[_]] { 3 def sum[A](xs: L[A])(C: Combine[A]): A 4 } 5 6 // Type-class instance for List 7 object ListAccumulateOver extends AccumulateOver[List] { 8 def sum[A](xs: List[A], C: Combine[A]): A = { 9 def go(acc: A)(rem: List[A]): A = rem match { 10 case y :: ys => go(C.combine(acc, y), ys) 11 case Nil => acc 12 } 13 go(C.start, xs) 14 } 15 } 16 17 def specialSum[F, A](xs: F[A])(C: Combine[A], L: AccumulateOver[F]): A = 18 L.sum(xs)(C) 19 20 // e.g. 21 val teamReputation: Int = 22 specialSum(reputations) 23 (OptionCombine(IntCombine), ListAccumulateOver) 24 .getOrElse(0)
  19. 1 implicit object IntCombine extends Combine[Int] { 2 ... 3

    } 4 5 implicit def OptionCombine[A](C: Combine[A]) extends Combine[Option[A]] { 6 ... 7 } 8 9 implicit object ListAccumulateOver extends AccumulateOver[List] { 10 ... 11 } 12 13 def specialSum[F, A](xs: F[A])(implicit C: Combine[A], L: AccumulateOver[F]): A = 14 L.sum(xs)(C) 15 16 // e.g. 17 val teamReputation: Int = specialSum(reputations).getOrElse(0)
  20. You know, Scalaz does all this already … 1 implicit

    object IntCombine extends Combine[Int] { 2 ... 3 } 4 5 implicit def OptionCombine[A](implicit C: Combine[A]) extends Combine[Option[A]] { 6 ... 7 } 8 9 implicit object ListAccumulateOver extends AccumulateOver[List] { 10 ... 11 } 12 13 def specialSum[F : AccumulateOver, A : Combine](xs: F[A]): A = 14 implicitly[AccumulateOver[F]].sum(xs)(implicitly[Combine[A]]) 15 16 // e.g. 17 val teamReputation: Int = specialSum(reputations).getOrElse(0)
  21. Typeclasses - Scalaz 1 trait Equal[A] { 2 def equal(a1:

    A, a2: A): Boolean 3 } 4 5 trait Order[A] extends Equal[A] { 6 def order(a: A1, a2: A): Ordering // LT, GT, EQ 7 } 8 9 trait Show[A] { 10 def show(a: A): List[Char] 11 } 12 13 trait Semigroup[A] { 14 def append(a1: A, a2: => A): A 15 } 16 17 trait Monoid[A] extends Semigroup[A] { 18 def zero: A 19 } 20 21 trait Foldable[F[_]] { 22 def foldMap[A, B](fa: F[A])(f: A => B)(implicit F: Monoid[B]): B 23 } Combine AccumulateOver
  22. 1 // Using Monoid[Option[Int]] and Foldable[List] 2 val teamReputation: Int

    = reputations.suml.getOrElse(0) 3 4 5 // And using Option syntax 6 val teamReputation: Int = reputations.suml | 0 // 27167
  23. Typeclasses - Argonaut 1 trait EncodeJson[A] { 2 def encode(x:

    A): Json 3 } 4 5 trait DecodeJson[A] { 6 def decode(j: Json): String \/ A // Not exactly 7 }
  24. Typeclasses - Scodec 1 trait Codec[A] { 2 def encode(a:

    A): String \/ BitVector 3 def decode(buffer: BitVector): String \/ (BitVector, A) 4 }
  25. 1 Numeric[A] // all number types, makes "best effort" to

    support operators 2 Fractional[A] // fractional number types, where / is true division 3 Integral[A] // integral number types, where / is floor division 4 Eq[A] // types that can be compared for equality 5 Order[A] // types that can be compared and ordered 6 Semigroup[A] // types with an associative binary operator |+| 7 Monoid[A] // semigroups that have an identity element 8 Group[A] // monoids that have an inverse operator 9 Semiring[A] // types that form semigroups under + and * 10 Rng[A] // types that form a group under + and a semigroup under * 11 Rig[A] // types that form monoids under + and * 12 Ring[A] // types that form a group under + and a monoid under * 13 Field[A] // euclidean rings with multiplicative inverses 14 Signed[A] // types that have a sign (negative, zero, positive) 15 NRoot[A] // types that support k-roots, logs, and fractional powers 16 Module[V,R] // types that form a left R-module 17 Trig[A] // types that support trigonometric functions 18 Bool[A] // types that form a boolean algebra Typeclasses - Spire
  26. 1 import scalaz._, Scalaz._ 2 3 def teamReputation(reputations: List[Option[Int]]): Int

    = 4 reputations.suml | 0 It looks like you’ve written a function but haven’t tested it …
  27. 1 import org.specs2._ 2 3 class TeamReputationSpec extends Specification {

    def is = s2""" 4 5 Team Reputation Specs 6 --------------------- 7 8 An empty team has 0 reputation $empty 9 A team with no reputation has 0 reputation $nones 10 A team's reputation is some of non-null reputations $somes 11 12 """ 13 14 def empty = 15 teamReputation(List()) must_== 0 16 17 def nones = 18 teamReputation(List(none[Int], none[Int])) must_== 0 19 20 def somes = 21 teamReputation(List(2.some, none[Int], 3.some)) must_== 5 22 }
  28. 1 import org.specs2._ 2 import org.scalacheck._ 3 4 class TeamReputationSpec

    extends Specification with ScalaCheck { def is = 5 s2""" 6 7 Team Reputation Specs 8 --------------------- 9 10 Team reputation order does not matter $order 11 Team reputations are additive $additive 12 13 """ 14 15 def order = prop((list: List[Option[Int]]) => 16 teamReputation(list) must_== teamReputation(list.reverse)) 17 18 def additive = 19 prop((list1: List[Option[Int]], list2: List[Option[Int]]) => { 20 teamReputation(list1 ++ list2) must_== 21 teamReputation(list1) + teamReputation(list2) 22 }) 23 }
  29. Summary • Encode as much as possible in types -

    don’t get lazy • Use abstractions that have already been implemented for you - e.g. typeclasses • Property-based testing FTW • Check out Typelevel Actually start doing this!
  30. References 1. Learning Scalaz - http://eed3si9n.com/learning- scalaz/ 2. Typelevel -

    http://typelevel.org 3. Typeclassopeia - http://www.haskell.org/ haskellwiki/Typeclassopedia