background-image: url(../img/fp-tower/website-background.svg) class: center, middle, white .title[Generic functions
part 2] --- # 1. Generic functions accept .hl[ALL] types
```scala def map[From, To](list: List[From])(update: From => To): List[To] ```
```scala map[String, Int](List(...))(...) map[User, Address](List(...))(...) ``` --- # 2. All types must be treated .hl[IN THE SAME WAY]
```scala def map[From, To](list: List[From])(update: From => To): List[To] = list match { case ints : List[Int] => ... case strings: List[String] => ... case users : List[User] => ... case _ => ... } ``` --- # All types must be treated .hl[IN THE SAME WAY]
.forty-seven-left[ ```scala def format[A](value: A): String = value match { case x: String => x.toLowerCase case x: Double => truncate(2, x) case _ => "N/A" } ``` ] .forty-seven-right[ ```scala format("Blue") // res0: String = "blue" format(123.123456) // res1: String = "123.12" format(true) // res2: String = "N/A" ``` ] --- class: medium-code # Why? Type erasure
.sixty-two-left[ ```scala def format[A](value: A): String = value match { case x: String => x.toLowerCase case x: Double => truncate(2, x) case x: List[String] => x.map(_.toLowerCase).mkString(",") case x: List[Double] => x.map(truncate(2, _)).mkString(",") case _ => "N/A" } ``` ] .thirty-two--right[ ```scala format("Blue") // res4: String = "blue" format(123.123456) // res5: String = "123.12" format(true) // res6: String = "N/A" format(List("Hello", "World")) // res7: String = "hello,world" ``` ] --
```scala format(List(123.123456, 0.1234)) // java.lang.ClassCastException: class java.lang.Double // cannot be cast to class java.lang.String ``` --- class: medium-code # Why? Type erasure
.forty-two-left[ ```scala def format[A](value: A): String = value match { case x: String => ... case x: Double => ... case x: List[String] => ... case x: List[Double] => ... case _ => ... } ``` ] .fifty-two-right[ ```scala def format[A](value: A): String = if(value.isInstanceOf[String]) ... else if(value.isInstanceOf[Double]) ... else if(value.isInstanceOf[List[String]]) ... else if(value.isInstanceOf[List[Double]]) ... else ... ``` ] --- class: medium-code # Why? Type erasure
.forty-two-left[ ```scala def format[A](value: A): String = value match { case x: String => ... case x: Double => ... case x: List[String] => ... case x: List[Double] => ... case _ => ... } ``` ] .fifty-two-right[ ```scala def format[A](value: A): String = if(value.isInstanceOf[String]) ... else if(value.isInstanceOf[Double]) ... else if(value.isInstanceOf[List[String]]) ... else if(value.isInstanceOf[List[Double]]) ... else ... ``` ]
.fifty-seven-left[ ```scala List(1.5,2.0).isInstanceOf[List[String]] // res8: Boolean = true ``` ```scala // warning: fruitless type test: a value of type // List[Double] cannot also be a List[String] ``` ] --- class: medium-code # Why? Poor documentation
```scala def format[A](value: A): String ``` --- class: center, middle # Parametric polymorphism (`map`) # != # Ad hoc polymorphism (`format`) --- # How can we implement `map`?
```scala def map[From, To](list: List[From])(update: From => To): List[To] ``` -- .fifty-seven-left[
] --
.medium[ 1. Return an empty list 1. Return an non empty list with values produced by update ] --- # Does it compile?
```scala def map[From, To](list: List[From])(update: From => To): List[To] = list ``` --- # Does it compile?
```scala def map[From, To](list: List[From])(update: From => To): List[To] = list // error: type mismatch; // found : List[From] // required: List[To] // list // ^^^^ ``` --- # Does it compile?
```scala def map[A](list: List[A])(update: A => A): List[A] = list // success ``` --- # Does it compile?
```scala def map(list: List[Int])(update: Int => Int): List[Int] = List(1, 2, 3) // success ``` --- # How can we test `map`?
```scala test("map"){ map(Nil)(_ => ???) shouldEqual Nil map(List(1,2,3) )(x => ???) shouldEqual ??? map(List(1,2,3,4,5))(x => ???) shouldEqual ??? map(List("Hello", "World") )(x => ???) shouldEqual ??? map(List("a", "b", "c", "d"))(x => ???) shouldEqual ??? } ``` -- .sixty-seven-left[.center[ ## All types must be treated in the same way ]] --- # How can we test `map`?
```scala test("map"){ map(Nil)(_ => ???) shouldEqual Nil map(List(1,2,3) )(_ + 1) shouldEqual List(2,3,4) map(List(1,2,3,4,5))(_ + 1) shouldEqual List(2,3,4,5,6) map(List(1,2,3) )(_ > 2) shouldEqual List(false,false,true) map(List(1,2,3,4,5))(_ > 2) shouldEqual List(false,false,true,true,true) } ``` -- .sixty-seven-left[.center[ ## All output elements must be produced by `update` ]] --- # How can we test `map`?
```scala test("map"){ map(Nil)(_ => ???) shouldEqual Nil map(List(1,2,3) )(x => x) shouldEqual List(1,2,3) map(List(1,2,3,4,5))(x => x) shouldEqual List(1,2,3,4,5) } ``` --- # How can we test `map`?
```scala test("map"){ map(Nil)(_ => ???) shouldEqual Nil map(List(1,2,3) )(identity) shouldEqual List(1,2,3) map(List(1,2,3,4,5))(identity) shouldEqual List(1,2,3,4,5) } ```
```scala:silent object Predef { def identity[A](value: A): A = value } ``` --- # Property Based Testing (PBT)
```scala test("map"){ forAll((list: List[Int]) => map(list)(identity) shouldEqual list ) } ``` --- class: white background-image: url(../img/fp-tower/website-background.svg)
# .white[Summary]
.medium[ * More reusable * Caller decides which type to use * Implementation must be generic * better documentation * less tests ] --- # Not everything in a language should be used
```scala class Spaceship { private def autoDestroy(): Unit = println("Boom!") } val shuttle = new Spaceship() ``` ```scala shuttle.autoDestroy() // error: method autoDestroy in class Spaceship cannot be accessed as a member of MdocApp1.this.Spaceship from class MdocApp1 in object MdocSession // shuttle.autoDestroy() // ^^^^^^^^^^^^^^^^^^^ ``` --- # Not everything in a language should be used
```scala class Spaceship { private def autoDestroy(): Unit = println("Boom!") } val shuttle = new Spaceship() ``` ```scala val method = classOf[Spaceship].getDeclaredMethod("autoDestroy") method.setAccessible(true) ``` ```scala method.invoke(shuttle) // Boom! ```