class: center, middle # Type classes from the ground up Nicolas Rinaudo • [@NicolasRinaudo] • [Besedo] --- class: center, middle # Parsing CSV --- ## CSV data ```csv 1,2,3 4,5,6 7,8,9 ``` ```scala type Cell = String type Row = List[Cell] type Csv = List[Row] ``` --- ## CSV data ```csv `1,2,3` `4,5,6` `7,8,9` ``` ```scala type Cell = String type Row = List[Cell] *type Csv = List[Row] ``` --- ## CSV data ```csv `1`,`2`,`3` 4,5,6 7,8,9 ``` ```scala type Cell = String *type Row = List[Cell] type Csv = List[Row] ``` --- ## CSV data ```csv `1`,2,3 4,5,6 7,8,9 ``` ```scala *type Cell = String type Row = List[Cell] type Csv = List[Row] ``` --- ## Parsing CSV ```csv 1,2,3 4,5,6 7,8,9 ``` ```scala def parseCsv(data: String): Csv = data. split("\n").toList. map(_.split(",").toList) ``` --- ## Parsing CSV ```csv *1,2,3 *4,5,6 *7,8,9 ``` ```scala def parseCsv(`data: String`): Csv = data. split("\n").toList. map(_.split(",").toList) ``` --- ## Parsing CSV ```csv *1,2,3 *4,5,6 *7,8,9 ``` ```scala def parseCsv(data: String): `Csv` = data. split("\n").toList. map(_.split(",").toList) ``` --- ## Parsing CSV ```csv `1,2,3` `4,5,6` `7,8,9` ``` ```scala def parseCsv(data: String): Csv = data. `split("\n")`.toList. map(_.split(",").toList) ``` --- ## Parsing CSV ```csv `1,2,3` `4,5,6` `7,8,9` ``` ```scala def parseCsv(data: String): Csv = data. split("\n").toList. `map`(_.split(",").toList) ``` --- ## Parsing CSV ```csv `1`,`2`,`3` `4`,`5`,`6` `7`,`8`,`9` ``` ```scala def parseCsv(data: String): Csv = data. split("\n").toList. map(`_.split(",")`.toList) ``` --- ## Parsing CSV ```scala val input = """1,2,3 |4,5,6 |7,8,9""" ``` -- ```scala parseCsv(input) // res0: Csv = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) ``` --- class: center, middle # Decoding CSV --- ## Decoding to `Int` ```csv 1,2,3 4,5,6 7,8,9 ``` ```scala parseCsv(input). map(_.map(_.toInt)) ``` --- ## Decoding to `Int` ```csv `1`,`2`,`3` `4`,`5`,`6` `7`,`8`,`9` ``` ```scala `parseCsv(input)`. map(_.map(_.toInt)) ``` --- ## Decoding to `Int` ```csv `1`,`2`,`3` 4,5,6 7,8,9 ``` ```scala parseCsv(input). `map`(_.map(_.toInt)) ``` --- ## Decoding to `Int` ```csv `1`,2,3 4,5,6 7,8,9 ``` ```scala parseCsv(input). map(_.`map`(_.toInt)) ``` --- ## Decoding to `Int` ```csv `1`,2,3 4,5,6 7,8,9 ``` ```scala parseCsv(input). map(_.map(_.`toInt`)) ``` --- ## Decoding to `Int` ```csv 1,2,3 4,5,6 7,8,9 ``` ```scala parseCsv(input). map(_.map(_.toInt)) // res1: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) ``` --- ## Decoding to `Int` ```scala def decodeCsv( input: String ): List[List[Int]] = parseCsv(input). map(_.map(_.toInt)) ``` --- ## Decoding to `Int` ```scala def decodeCsv( `input: String` ): List[List[Int]] = parseCsv(input). map(_.map(_.toInt)) ``` --- ## Decoding to `Int` ```scala def decodeCsv( input: String ): `List[List[Int]]` = parseCsv(input). map(_.map(_.toInt)) ``` --- ## Decoding to `Int` ```scala def decodeCsv( input: String ): List[List[Int]] = * parseCsv(input). * map(_.map(_.toInt)) ``` --- ## Decoding to `Int` ```scala def decodeCsv( input: String ): List[List[Int]] = parseCsv(input). map(_.map(`_.toInt`)) ``` --- ## Decoding to `Int` ```scala def decodeCsv( input : String, decodeCell: Cell => Int ): List[List[Int]] = parseCsv(input). map(_.map(decodeCell)) ``` --- ## Decoding to `Int` ```scala def decodeCsv( input : String, `decodeCell: Cell => Int` ): List[List[Int]] = parseCsv(input). map(_.map(decodeCell)) ``` --- ## Decoding to `Int` ```scala def decodeCsv( input : String, decodeCell: Cell => Int ): List[List[Int]] = parseCsv(input). map(_.map(`decodeCell`)) ``` --- ## Decoding to `Int` ```scala def decodeCsv( input : String, decodeCell: Cell => `Int` ): List[List[`Int`]] = parseCsv(input). map(_.map(decodeCell)) ``` --- ## Generic decoding ```scala def decodeCsv[A]( input : String, decodeCell: Cell => A ): List[List[A]] = parseCsv(input). map(_.map(decodeCell)) ``` --- ## Generic decoding ```scala def decodeCsv[`A`]( input : String, decodeCell: Cell => `A` ): List[List[`A`]] = parseCsv(input). map(_.map(decodeCell)) ``` --- ## Generic decoding ```scala decodeCsv(input, _.toInt) ``` --- ## Generic decoding ```scala decodeCsv(input, `_.toInt`) ``` --- ## Generic decoding ```scala decodeCsv(input, _.toInt) // res2: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) ``` --- ## Generic decoding ```scala decodeCsv(input, _.toFloat) ``` --- ## Generic decoding ```scala decodeCsv(input, `_.toFloat`) ``` --- ## Generic decoding ```scala decodeCsv(input, _.toFloat) // res3: List[List[Float]] = List(List(1.0, 2.0, 3.0), List(4.0, 5.0, 6.0), List(7.0, 8.0, 9.0)) ``` --- ## Implicit resolution > When a function expects a parameter of type `A` _and_ that parameter is marked as `implicit` _and_ there exists a > value of type `A` marked as `implicit` in scope, then the compiler will use that value if the parameter is unspecified. ```scala implicit val defaultInt: Int = 2 def printInt(implicit i: Int): Unit = println(i) ``` ```scala printInt ``` --- ## Implicit resolution > When .highlight[a function expects a parameter of type `A`] _and_ that parameter is marked as `implicit` _and_ there exists a > value of type `A` marked as `implicit` in scope, then the compiler will use that value if the parameter is unspecified. ```scala implicit val defaultInt: Int = 2 def printInt(implicit `i: Int`): Unit = println(i) ``` ```scala printInt ``` --- ## Implicit resolution > When a function expects a parameter of type `A` _and_ that parameter is .highlight[marked as `implicit`] _and_ there exists a > value of type `A` marked as `implicit` in scope, then the compiler will use that value if the parameter is unspecified. ```scala implicit val defaultInt: Int = 2 def printInt(`implicit` i: Int): Unit = println(i) ``` ```scala printInt ``` --- ## Implicit resolution > When a function expects a parameter of type `A` _and_ that parameter is marked as `implicit` _and_ there exists a > .highlight[value of type `A`] marked as `implicit` in scope, then the compiler will use that value if the parameter is unspecified. ```scala implicit `val defaultInt: Int` = 2 def printInt(implicit i: Int): Unit = println(i) ``` ```scala printInt ``` --- ## Implicit resolution > When a function expects a parameter of type `A` _and_ that parameter is marked as `implicit` _and_ there exists a > value of type `A` marked as .highlight[`implicit`] in scope, then the compiler will use that value if the parameter is unspecified. ```scala `implicit` val defaultInt: Int = 2 def printInt(implicit i: Int): Unit = println(i) ``` ```scala printInt ``` --- ## Implicit resolution > When a function expects a parameter of type `A` _and_ that parameter is marked as `implicit` _and_ there exists a > value of type `A` marked as `implicit` in scope, then the compiler will use that value .highlight[if the parameter is unspecified]. ```scala implicit val defaultInt: Int = 2 def printInt(implicit i: Int): Unit = println(i) ``` ```scala printInt` ` ``` --- ## Implicit resolution > When a function expects a parameter of type `A` _and_ that parameter is marked as `implicit` _and_ there exists a > value of type `A` marked as `implicit` in scope, then .highlight[the compiler will use that value] if the parameter is unspecified. ```scala implicit val defaultInt: Int = 2 def printInt(implicit i: Int): Unit = println(i) ``` ```scala printInt`(defaultInt)` ``` --- ## Implicit resolution > When a function expects a parameter of type `A` _and_ that parameter is marked as `implicit` _and_ there exists a > value of type `A` marked as `implicit` in scope, then .highlight[the compiler will use that value] if the parameter is unspecified. ```scala implicit val defaultInt: Int = 2 def printInt(implicit i: Int): Unit = println(i) ``` ```scala printInt(defaultInt) // 2 ``` --- ## Implicit resolution > When a function expects a parameter of type `A` _and_ that parameter is marked as `implicit` _and_ there exists a > value of type `A` marked as `implicit` in scope, then the compiler will use that value if the parameter is unspecified. ```scala implicit val defaultInt: Int = 2 def printInt(implicit i: Int): Unit = println(i) ``` ```scala printInt // 2 ``` --- ## Decoding with implicits ```scala def decodeCsv[A] (input: String) (implicit decodeCell: Cell => A) : List[List[A]] = parseCsv(input). map(_.map(decodeCell)) ``` --- ## Decoding with implicits ```scala def decodeCsv[A] (input: String) (`implicit decodeCell: Cell => A`) : List[List[A]] = parseCsv(input). map(_.map(decodeCell)) ``` --- ## Decoding with implicits ```scala implicit val strToInt: Cell => Int = Integer.parseInt ``` --- ## Decoding with implicits ```scala `implicit` val strToInt: `Cell => Int` = Integer.parseInt ``` --- ## Decoding with implicits ```scala decodeCsv[Int](input) ``` --- ## Decoding with implicits ```scala decodeCsv[`Int`](input) ``` --- ## Decoding with implicits ```scala decodeCsv[Int](input)`(???: Cell => Int)` ``` --- ## Decoding with implicits ```scala decodeCsv[Int](input)`(strToInt)` ``` --- ## Decoding with implicits ```scala decodeCsv[Int](input)(strToInt) // res7: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) ``` --- ## Decoding with implicits ```scala decodeCsv[Int](input) // res8: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) ``` --- ## The dangers of implicits > When the compiler finds a type `A` where it expects a type `B`, but there exists an implicit `A => B` > in scope, it will be applied silently. ```scala implicit val strToInt: String => Int = Integer.parseInt def add1(i: Int): Int = i + 1 ``` ```scala add1("123") ``` --- ## The dangers of implicits > When .highlight[the compiler finds a type `A`] where it expects a type `B`, but there exists an implicit `A => B` > in scope, it will be applied silently. ```scala implicit val strToInt: String => Int = Integer.parseInt def add1(i: Int): Int = i + 1 ``` ```scala add1(`"123"`) ``` --- ## The dangers of implicits > When the compiler finds a type `A` where .highlight[it expects a type `B`], but there exists an implicit `A => B` > in scope, it will be applied silently. ```scala implicit val strToInt: String => Int = Integer.parseInt def add1(`i: Int`): Int = i + 1 ``` ```scala add1("123") ``` --- ## The dangers of implicits > When the compiler finds a type `A` where it expects a type `B`, but .highlight[there exists an implicit `A => B`] > in scope, it will be applied silently. ```scala `implicit` val strToInt: `String => Int` = Integer.parseInt def add1(i: Int): Int = i + 1 ``` ```scala add1("123") ``` --- ## The dangers of implicits > When the compiler finds a type `A` where it expects a type `B`, but there exists an implicit `A => B` > in scope, .highlight[it will be applied silently]. ```scala implicit val strToInt: String => Int = Integer.parseInt def add1(i: Int): Int = i + 1 ``` ```scala add1(`strToInt("123")`) ``` --- ## The dangers of implicits > When the compiler finds a type `A` where it expects a type `B`, but there exists an implicit `A => B` > in scope, .highlight[it will be applied silently]. ```scala implicit val strToInt: String => Int = Integer.parseInt def add1(i: Int): Int = i + 1 ``` ```scala add1(strToInt("123")) // res10: Int = 124 ``` --- ## The dangers of implicits > When the compiler finds a type `A` where it expects a type `B`, but there exists an implicit `A => B` > in scope, it will be applied silently. ```scala implicit val strToInt: String => Int = Integer.parseInt def add1(i: Int): Int = i + 1 ``` ```scala add1("123") // res11: Int = 124 ``` --- ## Decoder type ```scala trait CellDecoder[A] { def decode(cell: Cell): A } ``` --- ## Decoder type ```scala trait `CellDecoder[A]` { def decode(cell: Cell): A } ``` --- ## Decoder type ```scala trait CellDecoder[A] { `def decode(cell: Cell): A` } ``` --- ## Decoder type ```scala trait CellDecoder[A] { def decode(`cell: Cell`): A } ``` --- ## Decoder type ```scala trait CellDecoder[A] { def decode(cell: Cell): `A` } ``` --- ## Decoder type ```scala object CellDecoder { def from[A]( f: Cell => A ) = new CellDecoder[A] { override def decode(cell: Cell) = f(cell) } } ``` --- ## Decoder type ```scala `object CellDecoder` { def from[A]( f: Cell => A ) = new CellDecoder[A] { override def decode(cell: Cell) = f(cell) } } ``` --- ## Decoder type ```scala object CellDecoder { `def from[A]`( f: Cell => A ) = `new CellDecoder[A]` { override def decode(cell: Cell) = f(cell) } } ``` --- ## Decoder type ```scala object CellDecoder { def from[A]( `f: Cell => A` ) = new CellDecoder[A] { override `def decode(cell: Cell) = f(cell)` } } ``` --- ## Decoder type ```scala implicit val intCellDecoder: CellDecoder[Int] = CellDecoder.from(_.toInt) implicit val floatCellDecoder: CellDecoder[Float] = CellDecoder.from(_.toFloat) implicit val stringCellDecoder: CellDecoder[String] = CellDecoder.from(identity) implicit val booleanCellDecoder: CellDecoder[Boolean] = CellDecoder.from(_.toBoolean) ``` --- ## Decoder type ```scala implicit val intCellDecoder: `CellDecoder[Int]` = CellDecoder.from(`_.toInt`) implicit val floatCellDecoder: CellDecoder[Float] = CellDecoder.from(_.toFloat) implicit val stringCellDecoder: CellDecoder[String] = CellDecoder.from(identity) implicit val booleanCellDecoder: CellDecoder[Boolean] = CellDecoder.from(_.toBoolean) ``` --- ## Decoder type ```scala implicit val intCellDecoder: CellDecoder[Int] = CellDecoder.from(_.toInt) implicit val floatCellDecoder: `CellDecoder[Float]` = CellDecoder.from(`_.toFloat`) implicit val stringCellDecoder: CellDecoder[String] = CellDecoder.from(identity) implicit val booleanCellDecoder: CellDecoder[Boolean] = CellDecoder.from(_.toBoolean) ``` --- ## Decoder type ```scala implicit val intCellDecoder: CellDecoder[Int] = CellDecoder.from(_.toInt) implicit val floatCellDecoder: CellDecoder[Float] = CellDecoder.from(_.toFloat) implicit val stringCellDecoder: `CellDecoder[String]` = CellDecoder.from(`identity`) implicit val booleanCellDecoder: CellDecoder[Boolean] = CellDecoder.from(_.toBoolean) ``` --- ## Decoder type ```scala implicit val intCellDecoder: CellDecoder[Int] = CellDecoder.from(_.toInt) implicit val floatCellDecoder: CellDecoder[Float] = CellDecoder.from(_.toFloat) implicit val stringCellDecoder: CellDecoder[String] = CellDecoder.from(identity) implicit val booleanCellDecoder: `CellDecoder[Boolean]` = CellDecoder.from(`_.toBoolean`) ``` --- ## Implicit decoder ```scala def decodeCsv[A] (input: String) (implicit da: CellDecoder[A]) : List[List[A]] = parseCsv(input). map(_.map(da.decode)) ``` --- ## Implicit decoder ```scala def decodeCsv[A] (input: String) (implicit `da: CellDecoder[A]`) : List[List[A]] = parseCsv(input). map(_.map(da.decode)) ``` --- ## Implicit decoder ```scala def decodeCsv[A] (input: String) (implicit da: CellDecoder[A]) : List[List[A]] = parseCsv(input). map(_.map(`da.decode`)) ``` --- ## Implicit decoder ```scala decodeCsv[Int](input) ``` --- ## Implicit decoder ```scala decodeCsv[`Int`](input) ``` --- ## Implicit decoder ```scala decodeCsv[Int](input)`(???: CellDecoder[Int])` ``` --- ## Implicit decoder ```scala decodeCsv[Int](input)`(intCellDecoder)` ``` --- ## Implicit decoder ```scala decodeCsv[Int](input)(intCellDecoder) // res13: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) ``` --- ## Implicit decoder ```scala decodeCsv[Int](input) // res14: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) ``` --- ## Implicit decoder ```scala decodeCsv[Float](input) ``` --- ## Implicit decoder ```scala decodeCsv[`Float`](input) ``` --- ## Implicit decoder ```scala decodeCsv[Float](input)`(???: CellDecoder[Float])` ``` --- ## Implicit decoder ```scala decodeCsv[Float](input)`(floatCellDecoder)` ``` --- ## Implicit decoder ```scala decodeCsv[Float](input)(floatCellDecoder) // res16: List[List[Float]] = List(List(1.0, 2.0, 3.0), List(4.0, 5.0, 6.0), List(7.0, 8.0, 9.0)) ``` --- ## Implicit decoder ```scala decodeCsv[Float](input) // res17: List[List[Float]] = List(List(1.0, 2.0, 3.0), List(4.0, 5.0, 6.0), List(7.0, 8.0, 9.0)) ``` --- ## Key takeaways -- We have made `decodeCsv` polymorphic by using: -- * parametric polymorphism. -- * implicit resolution. -- `CellDecoder` is known as a type class. --- class: center, middle # Could we do this in OOP? --- ## Subclassing ```java interface DecodableFromCell
{ A decodeCell(String cell); } ``` --- ## Subclassing ```java interface `DecodableFromCell
` { A decodeCell(String cell); } ``` --- ## Subclassing ```java interface DecodableFromCell
{ A `decodeCell`(String cell); } ``` --- ## Subclassing ```java interface DecodableFromCell
{ A decodeCell(`String cell`); } ``` --- ## Subclassing ```java interface DecodableFromCell
{ `A` decodeCell(String cell); } ``` --- ## Subclassing ```java public
> List
> decodeCsv(String csv) { Function
decode = ???; return parseCsv(csv) .stream() .map(row -> row .stream() .map(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Subclassing ```java public
> List
> `decodeCsv`(String csv) { Function
decode = ???; return parseCsv(csv) .stream() .map(row -> row .stream() .map(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Subclassing ```java public
> List
> decodeCsv(`String csv`) { Function
decode = ???; return parseCsv(csv) .stream() .map(row -> row .stream() .map(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Subclassing ```java public
> `List
>` decodeCsv(String csv) { Function
decode = ???; return parseCsv(csv) .stream() .map(row -> row .stream() .map(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Subclassing ```java public <`A extends DecodableFromCell
`> List
> decodeCsv(String csv) { Function
decode = ???; return parseCsv(csv) .stream() .map(row -> row .stream() .map(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Subclassing ```java public
> List
> decodeCsv(String csv) { Function
decode = ???; return `parseCsv(csv)` .stream() .map(row -> row .stream() .map(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Subclassing ```java public
> List
> decodeCsv(String csv) { Function
decode = ???; return parseCsv(csv) .stream() .`map`(row -> row .stream() .map(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Subclassing ```java public
> List
> decodeCsv(String csv) { Function
decode = ???; return parseCsv(csv) .stream() .map(`row` -> row .stream() .map(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Subclassing ```java public
> List
> decodeCsv(String csv) { Function
decode = ???; return parseCsv(csv) .stream() .map(row -> row .stream() .`map`(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Subclassing ```java public
> List
> decodeCsv(String csv) { Function
decode = ???; return parseCsv(csv) .stream() .map(row -> row .stream() .map(`decode`) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Subclassing ```java public
> List
> decodeCsv(String csv) { `Function
decode = ???`; return parseCsv(csv) .stream() .map(row -> row .stream() .map(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Explicit dictionary ```java interface CellDecoder
{ A decode(String cell); } ``` --- ## Explicit dictionary ```java interface `CellDecoder
` { A decode(String cell); } ``` --- ## Explicit dictionary ```java interface CellDecoder
{ `A decode(String cell);` } ``` --- ## Explicit dictionary ```java interface CellDecoder
{ A decode(`String cell`); } ``` --- ## Explicit dictionary ```java interface CellDecoder
{ `A` decode(String cell); } ``` --- ## Explicit dictionary ```java public
List
> decodeCsv(String csv, CellDecoder
decoder) { Function
decode = cell -> decoder.decode(cell); return parseCsv(csv) .stream() .map(row -> row .stream() .map(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Explicit dictionary ```java public
List
> decodeCsv(String csv, `CellDecoder
decoder`) { Function
decode = cell -> decoder.decode(cell); return parseCsv(csv) .stream() .map(row -> row .stream() .map(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Explicit dictionary ```java public
List
> decodeCsv(String csv, CellDecoder
decoder) { `Function
decode = cell -> decoder.decode(cell);` return parseCsv(csv) .stream() .map(row -> row .stream() .map(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Explicit dictionary ```java public
List
> decodeCsv(String csv, CellDecoder
decoder) { Function
decode = `cell -> decoder.decode(cell)`; return parseCsv(csv) .stream() .map(row -> row .stream() .map(decode) .collect(Collectors.toList())) .collect(Collectors.toList()); } ``` --- ## Explicit dictionary ```java CellDecoder
intCellDecoder = cell -> Integer.parseInt(cell); ``` --- ## Explicit dictionary ```java `CellDecoder
` intCellDecoder = cell -> Integer.parseInt(cell); ``` --- ## Explicit dictionary ```java CellDecoder
intCellDecoder = `cell -> Integer.parseInt(cell)`; ``` --- ## Explicit dictionary ```java decodeCsv("1,2,3\n4,5,6", intCellDecoder); ``` --- ## Explicit dictionary ```java decodeCsv(`"1,2,3\n4,5,6"`, intCellDecoder); ``` --- ## Explicit dictionary ```java decodeCsv("1,2,3\n4,5,6", `intCellDecoder`); ``` --- ## Explicit dictionary ```java decodeCsv("1,2,3\n4,5,6", intCellDecoder); // [[1, 2, 3], [4, 5, 6]] ``` --- ## Key takeaways -- * subclassing requires values where type classes don't. -- * type classes are "merely" syntactic sugar over explicit dictionaries. --- class: center, middle # Implicit type class composition --- ## Heterogenous types ```csv 1997,Ford 2000,Mercury ``` --- ## Heterogenous types ```csv `1997`,Ford `2000`,Mercury ``` --- ## Heterogenous types ```csv 1997,`Ford` 2000,`Mercury` ``` --- ## `RowDecoder` type class ```scala trait RowDecoder[A] { def decode(row: Row): A } ``` --- ## `RowDecoder` type class ```scala trait `RowDecoder[A]` { def decode(row: Row): A } ``` --- ## `RowDecoder` type class ```scala trait RowDecoder[A] { `def decode(row: Row): A` } ``` --- ## `RowDecoder` type class ```scala trait RowDecoder[A] { def decode(`row: Row`): A } ``` --- ## `RowDecoder` type class ```scala trait RowDecoder[A] { def decode(row: Row): `A` } ``` --- ## `RowDecoder` type class ```scala object RowDecoder { def from[A]( f: Row => A ) = new RowDecoder[A] { override def decode(row: Row) = f(row) } } ``` --- ## `RowDecoder` type class ```scala `object RowDecoder` { def from[A]( f: Row => A ) = new RowDecoder[A] { override def decode(row: Row) = f(row) } } ``` --- ## `RowDecoder` type class ```scala object RowDecoder { `def from[A]`( f: Row => A ) = `new RowDecoder[A]` { override def decode(row: Row) = f(row) } } ``` --- ## `RowDecoder` type class ```scala object RowDecoder { def from[A]( `f: Row => A` ) = new RowDecoder[A] { override `def decode(row: Row) = f(row)` } } ``` --- ## `RowDecoder` type class ```scala def decodeCsv[A](input: String) (implicit da: RowDecoder[A]): List[A] = parseCsv(input). map(da.decode) ``` --- ## `RowDecoder` type class ```scala def decodeCsv[A](input: String) (implicit `da: RowDecoder[A]`): List[A] = parseCsv(input). map(da.decode) ``` --- ## `RowDecoder` type class ```scala def decodeCsv[A](input: String) (implicit da: RowDecoder[A]): List[A] = parseCsv(input). `map(da.decode)` ``` --- ## `RowDecoder` type class ```scala def decodeCsv[A](input: String) (implicit da: RowDecoder[A]): `List[A]` = parseCsv(input). map(da.decode) ``` --- ## `(Int, String)` decoder ```scala implicit val tupleDecoder = RowDecoder.from[(Int, String)] { row => ( row(0).toInt, row(1) ) } ``` --- ## `(Int, String)` decoder ```scala `implicit` val tupleDecoder = `RowDecoder`.from[`(Int, String)`] { row => ( row(0).toInt, row(1) ) } ``` --- ## `(Int, String)` decoder ```scala implicit val tupleDecoder = RowDecoder.from[(Int, String)] { `row` => ( row(0).toInt, row(1) ) } ``` --- ## `(Int, String)` decoder ```scala implicit val tupleDecoder = RowDecoder.from[(Int, String)] { row => ( `row(0).toInt`, row(1) ) } ``` --- ## `(Int, String)` decoder ```scala implicit val tupleDecoder = RowDecoder.from[(Int, String)] { row => ( row(0).toInt, `row(1)` ) } ``` --- ## `(Int, String)` decoder ```scala implicit val tupleDecoder = RowDecoder.from[(Int, String)] { row => `(` row(0).toInt, row(1) `)` } ``` --- ## `(Int, String)` decoder ```scala implicit val tupleDecoder = RowDecoder.from[(Int, String)] { row => ( `row(0).toInt`, `row(1)` ) } ``` --- ## `(Int, String)` decoder ```scala implicit val tupleDecoder = RowDecoder.from[(Int, String)] { row => ( intCellDecoder.decode(row(0)), stringCellDecoder.decode(row(1)) ) } ``` --- ## `(Int, String)` decoder ```scala implicit val tupleDecoder = RowDecoder.from[(Int, String)] { row => ( `intCellDecoder.decode(row(0))`, `stringCellDecoder.decode(row(1))` ) } ``` --- ## `(Int, String)` decoder ```scala implicit `val` tupleDecoder = RowDecoder.from[(Int, String)] { row => ( intCellDecoder.decode(row(0)), stringCellDecoder.decode(row(1)) ) } ``` --- ## Implicit resolution revisited > When the compiler looks for an implicit value of type `A` and finds an implicit function that returns a `A` that > it can call, it will use its return value. ```scala implicit val defaultDouble: Double = 3.0 implicit def getFloat(implicit d: Double): Float = d.toFloat def printFloat(implicit f: Float): Unit = println(f) ``` ```scala printFloat ``` --- ## Implicit resolution revisited > When the .highlight[compiler looks for an implicit value of type `A`] and finds an implicit function that returns a `A` that > it can call, it will use its return value. ```scala implicit val defaultDouble: Double = 3.0 implicit def getFloat(implicit d: Double): Float = d.toFloat def printFloat(implicit f: Float): Unit = println(f) ``` ```scala printFloat` ` ``` --- ## Implicit resolution revisited > When the .highlight[compiler looks for an implicit value of type `A`] and finds an implicit function that returns a `A` that > it can call, it will use its return value. ```scala implicit val defaultDouble: Double = 3.0 implicit def getFloat(implicit d: Double): Float = d.toFloat def printFloat(`implicit f: Float`): Unit = println(f) ``` ```scala printFloat ``` --- ## Implicit resolution revisited > When the compiler looks for an implicit value of type `A` and .highlight[finds an implicit function that returns a `A`] that > it can call, it will use its return value. ```scala implicit val defaultDouble: Double = 3.0 `implicit def getFloat`(implicit d: Double): `Float` = d.toFloat def printFloat(implicit f: Float): Unit = println(f) ``` ```scala printFloat ``` --- ## Implicit resolution revisited > When the compiler looks for an implicit value of type `A` and finds an implicit function that returns a `A` that > .highlight[it can call], it will use its return value. ```scala implicit val defaultDouble: Double = 3.0 implicit def getFloat(`implicit d: Double`): Float = d.toFloat def printFloat(implicit f: Float): Unit = println(f) ``` ```scala printFloat ``` --- ## Implicit resolution revisited > When the compiler looks for an implicit value of type `A` and finds an implicit function that returns a `A` that > .highlight[it can call], it will use its return value. ```scala `implicit val defaultDouble: Double` = 3.0 implicit def getFloat(implicit d: Double): Float = d.toFloat def printFloat(implicit f: Float): Unit = println(f) ``` ```scala printFloat ``` --- ## Implicit resolution revisited > When the compiler looks for an implicit value of type `A` and finds an implicit function that returns a `A` that > it can call, .highlight[it will use its return value]. ```scala implicit val defaultDouble: Double = 3.0 implicit def getFloat(implicit d: Double): Float = d.toFloat def printFloat(implicit f: Float): Unit = println(f) ``` ```scala printFloat`(getFloat)` ``` --- ## Implicit resolution revisited > When the compiler looks for an implicit value of type `A` and finds an implicit function that returns a `A` that > it can call, .highlight[it will use its return value]. ```scala implicit val defaultDouble: Double = 3.0 implicit def getFloat(implicit d: Double): Float = d.toFloat def printFloat(implicit f: Float): Unit = println(f) ``` ```scala printFloat`(getFloat(defaultDouble))` ``` --- ## Implicit resolution revisited > When the compiler looks for an implicit value of type `A` and finds an implicit function that returns a `A` that > it can call, .highlight[it will use its return value]. ```scala implicit val defaultDouble: Double = 3.0 implicit def getFloat(implicit d: Double): Float = d.toFloat def printFloat(implicit f: Float): Unit = println(f) ``` ```scala printFloat(getFloat(defaultDouble)) // 3.0 ``` --- ## Implicit resolution revisited > When the compiler looks for an implicit value of type `A` and finds an implicit function that returns a `A` that > it can call, .highlight[it will use its return value]. ```scala implicit val defaultDouble: Double = 3.0 implicit def getFloat(implicit d: Double): Float = d.toFloat def printFloat(implicit f: Float): Unit = println(f) ``` ```scala printFloat // 3.0 ``` --- ## `(Int, String)` decoder ```scala implicit def tupleDecoder( implicit da: CellDecoder[Int], db: CellDecoder[String] ) = RowDecoder.from[(Int, String)] { row => ( da.decode(row(0)), db.decode(row(1)) ) } ``` --- ## `(Int, String)` decoder ```scala implicit `def` tupleDecoder( implicit da: CellDecoder[Int], db: CellDecoder[String] ) = RowDecoder.from[(Int, String)] { row => ( da.decode(row(0)), db.decode(row(1)) ) } ``` --- ## `(Int, String)` decoder ```scala implicit def tupleDecoder( implicit `da: CellDecoder[Int]`, db: CellDecoder[String] ) = RowDecoder.from[(Int, String)] { row => ( `da.decode(row(0))`, db.decode(row(1)) ) } ``` --- ## `(Int, String)` decoder ```scala implicit def tupleDecoder( implicit da: CellDecoder[Int], `db: CellDecoder[String]` ) = RowDecoder.from[(Int, String)] { row => ( da.decode(row(0)), `db.decode(row(1))` ) } ``` --- ## `(Int, String)` decoder ```scala implicit def tupleDecoder( `implicit` da: CellDecoder[Int], db: CellDecoder[String] ) = RowDecoder.from[(Int, String)] { row => ( da.decode(row(0)), db.decode(row(1)) ) } ``` --- ## `(Int, String)` decoder ```scala implicit def tupleDecoder( implicit da: CellDecoder[`Int`], db: CellDecoder[`String`] ) = RowDecoder.from[`(Int, String)`] { row => ( da.decode(row(0)), db.decode(row(1)) ) } ``` --- ## `(A, B)` decoder ```scala implicit def tupleDecoder[A, B]( implicit da: CellDecoder[A], db: CellDecoder[B] ) = RowDecoder.from[(A, B)] { row => ( da.decode(row(0)), db.decode(row(1)) ) } ``` --- ## `(A, B)` decoder ```scala implicit def tupleDecoder[`A, B`]( implicit da: CellDecoder[`A`], db: CellDecoder[`B`] ) = RowDecoder.from[`(A, B)`] { row => ( da.decode(row(0)), db.decode(row(1)) ) } ``` --- ## `(A, B)` decoder ```scala implicit def tupleDecoder[A, B]( implicit da: `CellDecoder[A]`, db: CellDecoder[B] ) = RowDecoder.from[(A, B)] { row => ( da.decode(row(0)), db.decode(row(1)) ) } ``` --- ## `(A, B)` decoder ```scala implicit def tupleDecoder[A, B]( implicit da: CellDecoder[A], db: `CellDecoder[B]` ) = RowDecoder.from[(A, B)] { row => ( da.decode(row(0)), db.decode(row(1)) ) } ``` --- ## `(A, B)` decoder ```scala implicit def tupleDecoder[A, B]( implicit da: CellDecoder[A], db: CellDecoder[B] ) = `RowDecoder`.from[`(A, B)`] { row => ( da.decode(row(0)), db.decode(row(1)) ) } ``` --- ## Heterogenous types ```scala val input = """1997,Ford |2000,Mercury""" ``` --- ## Heterogenous types ```scala decodeCsv[(Int, String)](input) ``` --- ## Heterogenous types ```scala decodeCsv[`(Int, String)`](input) ``` --- ## Heterogenous types ```scala decodeCsv[(Int, String)](input)`(???: RowDecoder[(Int, String]))` ``` --- ## Heterogenous types ```scala decodeCsv[(Int, String)](input)`(tupleDecoder[Int, String])` ``` --- ## Heterogenous types ```scala decodeCsv[(Int, String)](input)(tupleDecoder[Int, String]`(` `???: CellDecoder[Int]`, `???: CellDecoder[String]` `)`) ``` --- ## Heterogenous types ```scala decodeCsv[(Int, String)](input)(tupleDecoder[Int, String]( `???: CellDecoder[Int]`, ???: CellDecoder[String] )) ``` --- ## Heterogenous types ```scala decodeCsv[(Int, String)](input)(tupleDecoder[Int, String]( `intCellDecoder`, ???: CellDecoder[String] )) ``` --- ## Heterogenous types ```scala decodeCsv[(Int, String)](input)(tupleDecoder[Int, String]( intCellDecoder, `???: CellDecoder[String]` )) ``` --- ## Heterogenous types ```scala decodeCsv[(Int, String)](input)(tupleDecoder[Int, String]( intCellDecoder, `stringCellDecoder` )) ``` --- ## Heterogenous types ```scala decodeCsv[(Int, String)](input)(tupleDecoder[Int, String]( intCellDecoder, stringCellDecoder )) // res22: List[(Int, String)] = List((1997,Ford), (2000,Mercury)) ``` --- ## Heterogenous types ```scala decodeCsv[(Int, String)](input) // res23: List[(Int, String)] = List((1997,Ford), (2000,Mercury)) ``` --- ## Collections of values ```csv 1,2,3 4,5,6 7,8,9 ``` --- ## Collections of values ```scala implicit def listDecoder[A]( implicit da: CellDecoder[A] ) = RowDecoder.from[List[A]] { row => row.map(da.decode) } ``` --- ## Collections of values ```scala implicit def listDecoder[`A`]( `implicit` da: `CellDecoder[A]` ) = RowDecoder.from[List[A]] { row => row.map(da.decode) } ``` --- ## Collections of values ```scala implicit def listDecoder[A]( implicit da: CellDecoder[A] ) = `RowDecoder`.from[`List[A]`] { row => row.map(da.decode) } ``` --- ## Collections of values ```scala implicit def listDecoder[A]( implicit da: CellDecoder[A] ) = RowDecoder.from[List[A]] { row => `row.map(da.decode)` } ``` --- ## Collections of values ```scala val input = """1,2,3 |4,5,6 |7,8,9""" ``` --- ## Collections of values ```scala decodeCsv[List[Int]](input) ``` --- ## Collections of values ```scala decodeCsv[`List[Int]`](input) ``` --- ## Collections of values ```scala decodeCsv[List[Int]](input)`(???: RowDecoder[List[Int]])` ``` --- ## Collections of values ```scala decodeCsv[List[Int]](input)(`listDecoder[Int]`) ``` --- ## Collections of values ```scala decodeCsv[List[Int]](input)(listDecoder[Int]`(` `???: CellDecoder[Int]` `)`) ``` --- ## Collections of values ```scala decodeCsv[List[Int]](input)(listDecoder[Int]( `intCellDecoder` )) ``` --- ## Collections of values ```scala decodeCsv[List[Int]](input)(listDecoder[Int]( intCellDecoder )) // res25: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) ``` --- ## Collections of values ```scala decodeCsv[List[Int]](input) // res26: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) ``` --- ## Optional cells ```csv 1997,Ford ,Mercury ``` --- ## Optional cells ```csv `1997`,Ford ,Mercury ``` --- ## Optional cells ```csv 1997,Ford ` `,Mercury ``` --- ## Optional cells ```scala implicit def optionCellDecoder[A]( implicit da: CellDecoder[A] ) = CellDecoder.from[Option[A]] { cell => if(cell.trim.isEmpty) None else Some(da.decode(cell)) } ``` --- ## Optional cells ```scala implicit def optionCellDecoder[`A`]( `implicit` da: `CellDecoder[A]` ) = CellDecoder.from[Option[A]] { cell => if(cell.trim.isEmpty) None else Some(da.decode(cell)) } ``` --- ## Optional cells ```scala implicit def optionCellDecoder[A]( implicit da: CellDecoder[A] ) = `CellDecoder`.from[`Option[A]`] { cell => if(cell.trim.isEmpty) None else Some(da.decode(cell)) } ``` --- ## Optional cells ```scala implicit def optionCellDecoder[A]( implicit da: CellDecoder[A] ) = CellDecoder.from[Option[A]] { cell => `if(cell.trim.isEmpty) None` else Some(da.decode(cell)) } ``` --- ## Optional cells ```scala implicit def optionCellDecoder[A]( implicit da: CellDecoder[A] ) = CellDecoder.from[Option[A]] { cell => if(cell.trim.isEmpty) None `else Some(da.decode(cell))` } ``` --- ## Optional cells ```scala implicit def optionCellDecoder[A]( implicit da: CellDecoder[A] ) = CellDecoder.from[Option[A]] { cell => if(cell.trim.isEmpty) None else Some(`da.decode(cell)`) } ``` --- ## Optional cells ```scala implicit def optionCellDecoder[A]( implicit da: CellDecoder[A] ) = CellDecoder.from[Option[A]] { cell => if(cell.trim.isEmpty) None else `Some(da.decode(cell))` } ``` --- ## Optional cells ```scala val input = """1997,Ford | ,Mercury""" ``` --- ## Optional cells ```scala decodeCsv[(Option[Int], String)](input) ``` --- ## Optional cells ```scala decodeCsv[`(Option[Int], String)`](input) ``` --- ## Optional cells ```scala decodeCsv[(Option[Int], String)](input)`(` `???: RowDecoder[(Option[Int], String)]` `)` ``` --- ## Optional cells ```scala decodeCsv[(Option[Int], String)](input)( `tupleDecoder[Option[Int], String]` ) ``` --- ## Optional cells ```scala decodeCsv[(Option[Int], String)](input)( tupleDecoder[Option[Int], String]`(` `???: CellDecoder[Option[Int]]`, `???: CellDecoder[String]` `)`) ``` --- ## Optional cells ```scala decodeCsv[(Option[Int], String)](input)( tupleDecoder[Option[Int], String]( `???: CellDecoder[Option[Int]]`, ???: CellDecoder[String] )) ``` --- ## Optional cells ```scala decodeCsv[(Option[Int], String)](input)( tupleDecoder[Option[Int], String]( `optionCellDecoder[Int]`, ???: CellDecoder[String] )) ``` --- ## Optional cells ```scala decodeCsv[(Option[Int], String)](input)( tupleDecoder[Option[Int], String]( optionCellDecoder[Int]`(???: CellDecoder[Int])`, ???: CellDecoder[String] )) ``` --- ## Optional cells ```scala decodeCsv[(Option[Int], String)](input)( tupleDecoder[Option[Int], String]( optionCellDecoder[Int](`intCellDecoder`), ???: CellDecoder[String] )) ``` --- ## Optional cells ```scala decodeCsv[(Option[Int], String)](input)( tupleDecoder[Option[Int], String]( optionCellDecoder[Int](intCellDecoder), `???: CellDecoder[String]` )) ``` --- ## Optional cells ```scala decodeCsv[(Option[Int], String)](input)( tupleDecoder[Option[Int], String]( optionCellDecoder[Int](intCellDecoder), `stringCellDecoder` )) ``` --- ## Optional cells ```scala decodeCsv[(Option[Int], String)](input)( tupleDecoder[Option[Int], String]( optionCellDecoder[Int](intCellDecoder), stringCellDecoder )) // res28: List[(Option[Int], String)] = List((Some(1997),Ford), (None,Mercury)) ``` --- ## Optional cells ```scala decodeCsv[(Option[Int], String)](input) // res29: List[(Option[Int], String)] = List((Some(1997),Ford), (None,Mercury)) ``` --- ## Cells with multiple types ```csv 1997,Ford true,Mercury ``` --- ## Cells with multiple types ```csv `1997`,Ford true,Mercury ``` --- ## Cells with multiple types ```csv 1997,Ford `true`,Mercury ``` --- ## Cells with multiple types ```scala implicit def eitherCellDecoder[A, B]( implicit da: CellDecoder[A], db: CellDecoder[B] ) = CellDecoder.from[Either[A, B]] { cell => try { Left(da.decode(cell)) } catch { case _: Throwable => Right(db.decode(cell)) } } ``` --- ## Cells with multiple types ```scala implicit def eitherCellDecoder[`A`, B]( `implicit` da: `CellDecoder[A]`, db: CellDecoder[B] ) = CellDecoder.from[Either[A, B]] { cell => try { Left(da.decode(cell)) } catch { case _: Throwable => Right(db.decode(cell)) } } ``` --- ## Cells with multiple types ```scala implicit def eitherCellDecoder[A, `B`]( `implicit` da: CellDecoder[A], db: `CellDecoder[B]` ) = CellDecoder.from[Either[A, B]] { cell => try { Left(da.decode(cell)) } catch { case _: Throwable => Right(db.decode(cell)) } } ``` --- ## Cells with multiple types ```scala implicit def eitherCellDecoder[A, B]( implicit da: CellDecoder[A], db: CellDecoder[B] ) = `CellDecoder`.from[`Either[A, B]`] { cell => try { Left(da.decode(cell)) } catch { case _: Throwable => Right(db.decode(cell)) } } ``` --- ## Cells with multiple types ```scala implicit def eitherCellDecoder[A, B]( implicit da: CellDecoder[A], db: CellDecoder[B] ) = CellDecoder.from[Either[A, B]] { cell => try { `Left(da.decode(cell))` } catch { case _: Throwable => Right(db.decode(cell)) } } ``` --- ## Cells with multiple types ```scala implicit def eitherCellDecoder[A, B]( implicit da: CellDecoder[A], db: CellDecoder[B] ) = CellDecoder.from[Either[A, B]] { cell => try { Left(da.decode(cell)) } catch { case _: Throwable => `Right(db.decode(cell))` } } ``` --- ## Cells with multiple types ```scala val input = """1997,Ford |true,Mercury""" ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input) ``` --- ## Cells with multiple types ```scala decodeCsv[`(Either[Int, Boolean], String)`](input) ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input)`(` `???: RowDecoder[(Either[Int, Boolean], String)]` `)` ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input)( `tupleDecoder[Either[Int, Boolean], String]` ) ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input)( tupleDecoder[Either[Int, Boolean], String]`(` `???: CellDecoder[Either[Int, Boolean]]`, `???: CellDecoder[String]` `)`) ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input)( tupleDecoder[Either[Int, Boolean], String]( `???: CellDecoder[Either[Int, Boolean]]`, ???: CellDecoder[String] )) ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input)( tupleDecoder[Either[Int, Boolean], String]( `eitherCellDecoder[Int, Boolean]`, ???: CellDecoder[String] )) ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input)( tupleDecoder[Either[Int, Boolean], String]( eitherCellDecoder[Int, Boolean]`(` `???: CellDecoder[Int]`, `???: CellDecoder[Boolean]` `)`, ???: CellDecoder[String] )) ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input)( tupleDecoder[Either[Int, Boolean], String]( eitherCellDecoder[Int, Boolean]( `???: CellDecoder[Int]`, ???: CellDecoder[Boolean] ), ???: CellDecoder[String] )) ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input)( tupleDecoder[Either[Int, Boolean], String]( eitherCellDecoder[Int, Boolean]( `intCellDecoder`, ???: CellDecoder[Boolean] ), ???: CellDecoder[String] )) ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input)( tupleDecoder[Either[Int, Boolean], String]( eitherCellDecoder[Int, Boolean]( intCellDecoder, `???: CellDecoder[Boolean]` ), ???: CellDecoder[String] )) ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input)( tupleDecoder[Either[Int, Boolean], String]( eitherCellDecoder[Int, Boolean]( intCellDecoder, `booleanCellDecoder` ), ???: CellDecoder[String] )) ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input)( tupleDecoder[Either[Int, Boolean], String]( eitherCellDecoder[Int, Boolean]( intCellDecoder, booleanCellDecoder ), `???: CellDecoder[String]` )) ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input)( tupleDecoder[Either[Int, Boolean], String]( eitherCellDecoder[Int, Boolean]( intCellDecoder, booleanCellDecoder ), `stringCellDecoder` )) ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input)( tupleDecoder[Either[Int, Boolean], String]( eitherCellDecoder[Int, Boolean]( intCellDecoder, booleanCellDecoder ), stringCellDecoder )) // res31: List[(Either[Int,Boolean], String)] = List((Left(1997),Ford), (Right(true),Mercury)) ``` --- ## Cells with multiple types ```scala decodeCsv[(Either[Int, Boolean], String)](input) // res32: List[(Either[Int,Boolean], String)] = List((Left(1997),Ford), (Right(true),Mercury)) ``` --- ## Going nuts ```csv 1997,Ford true,Mercury 2007, ``` --- ## Going nuts ```csv `1997`,Ford `true`,Mercury 2007, ``` --- ## Going nuts ```csv 1997,`Ford` true,Mercury 2007,` ` ``` --- ## Going nuts ```scala val input = """1997,Ford |true,Mercury |2007, """ ``` --- ## Going nuts ```scala decodeCsv[List[Either[Either[Int, Boolean], Option[String]]]]( input ) ``` --- ## Going nuts ```scala decodeCsv[`List[Either[Either[Int, Boolean], Option[String]]]`]( input ) ``` --- ## Going nuts ```scala decodeCsv[List[Either[Either[Int, Boolean], Option[String]]]]( input )( * listDecoder[Either[Either[Int, Boolean], Option[String]]]( * eitherCellDecoder[Either[Int, Boolean], Option[String]]( * eitherCellDecoder[Int, Boolean]( * intCellDecoder, * booleanCellDecoder * ), * optionCellDecoder[String](stringCellDecoder) * ) * ) ) ``` --- ## Going nuts ```scala decodeCsv[List[Either[Either[Int, Boolean], Option[String]]]]( input )( listDecoder[Either[Either[Int, Boolean], Option[String]]]( eitherCellDecoder[Either[Int, Boolean], Option[String]]( eitherCellDecoder[Int, Boolean]( intCellDecoder, booleanCellDecoder ), optionCellDecoder[String](stringCellDecoder) ) ) ) // res33: List[List[Either[Either[Int,Boolean],Option[String]]]] = List(List(Left(Left(1997)), Right(Some(Ford))), List(Left(Right(true)), Right(Some(Mercury))), List(Left(Left(2007)), Right(None))) ``` --- ## Going nuts ```scala decodeCsv[List[Either[Either[Int, Boolean], Option[String]]]]( input ) // res34: List[List[Either[Either[Int,Boolean],Option[String]]]] = List(List(Left(Left(1997)), Right(Some(Ford))), List(Left(Right(true)), Right(Some(Mercury))), List(Left(Left(2007)), Right(None))) ``` --- ## Key takeaways -- * Type classes are strictly syntactic sugar over explicit dictionaries. -- * But what great syntactic sugar they are! --- class: center, middle # Making the syntax sweeter --- ## Context bounds ```scala def decodeCsv[A](input: String) (implicit da: RowDecoder[A]): List[A] = parseCsv(input). map(da.decode) ``` --- ## Context bounds ```scala def decodeCsv[A](input: String) (`implicit da: RowDecoder[A]`): List[A] = parseCsv(input). map(da.decode) ``` --- ## Context bounds ```scala def decodeCsv[A: RowDecoder](input: String): List[A] = parseCsv(input). map(implicitly[RowDecoder[A]].decode) ``` --- ## Context bounds ```scala def decodeCsv[`A: RowDecoder`](input: String): List[A] = parseCsv(input). map(implicitly[RowDecoder[A]].decode) ``` --- ## Context bounds ```scala def decodeCsv[A: RowDecoder](input: String): List[A] = parseCsv(input). map(`implicitly[RowDecoder[A]]`.decode) ``` --- ## Instance summoning ```scala object RowDecoder { def from[A]( f: Row => A ) = new RowDecoder[A] { override def decode(row: Row) = f(row) } def apply[A](implicit da: RowDecoder[A]): RowDecoder[A] = da } ``` --- ## Instance summoning ```scala object RowDecoder { def from[A]( f: Row => A ) = new RowDecoder[A] { override def decode(row: Row) = f(row) } `def apply[A](implicit da: RowDecoder[A]): RowDecoder[A] = da` } ``` --- ## Instance summoning ```scala object RowDecoder { def from[A]( f: Row => A ) = new RowDecoder[A] { override def decode(row: Row) = f(row) } def apply[`A`](implicit da: RowDecoder[A]): RowDecoder[A] = da } ``` --- ## Instance summoning ```scala object RowDecoder { def from[A]( f: Row => A ) = new RowDecoder[A] { override def decode(row: Row) = f(row) } def apply[A](implicit `da: RowDecoder[A]`): RowDecoder[A] = da } ``` --- ## Instance summoning ```scala object RowDecoder { def from[A]( f: Row => A ) = new RowDecoder[A] { override def decode(row: Row) = f(row) } def apply[A](implicit da: RowDecoder[A]): RowDecoder[A] = `da` } ``` --- ## Instance summoning ```scala def decodeCsv[A: RowDecoder](input: String): List[A] = parseCsv(input). map(RowDecoder[A].decode) ``` --- ## Instance summoning ```scala def decodeCsv[A: RowDecoder](input: String): List[A] = parseCsv(input). map(`RowDecoder[A]`.decode) ``` --- ## Extension methods ```scala implicit class RowDecoderOps(row: Row) { def decodeRow[A: RowDecoder]: A = RowDecoder[A].decode(row) } ``` --- ## Extension methods ```scala `implicit` class RowDecoderOps(row: Row) { def decodeRow[A: RowDecoder]: A = RowDecoder[A].decode(row) } ``` --- ## Extension methods ```scala implicit class RowDecoderOps(`row: Row`) { def decodeRow[A: RowDecoder]: A = RowDecoder[A].decode(row) } ``` --- ## Extension methods ```scala implicit class RowDecoderOps(row: Row) { def decodeRow[`A: RowDecoder`]: A = RowDecoder[A].decode(row) } ``` --- ## Extension methods ```scala implicit class RowDecoderOps(row: Row) { def `decodeRow`[A: RowDecoder]: A = RowDecoder[A].decode(row) } ``` --- ## Extension methods ```scala implicit class RowDecoderOps(row: Row) { def decodeRow[A: RowDecoder]: `A` = RowDecoder[A].decode(row) } ``` --- ## Extension methods ```scala implicit class RowDecoderOps(row: Row) { def decodeRow[A: RowDecoder]: A = `RowDecoder[A].decode(row)` } ``` --- ## Extension methods ```scala def decodeCsv[A: RowDecoder](input: String): List[A] = parseCsv(input). map(_.decodeRow) ``` --- ## Extension methods ```scala def decodeCsv[A: RowDecoder](input: String): List[A] = parseCsv(input). map(`_.decodeRow`) ``` --- ## Scala 3 extension methods ```scala trait RowDecoder[A]: def decode(row: Row): A def (row: Row) decodeRow: A = decode(row) ``` --- ## Scala 3 extension methods ```scala trait RowDecoder[A]: def decode(row: Row): A `def (row: Row) decodeRow: A = decode(row)` ``` --- ## Scala 3 extension methods ```scala trait RowDecoder[A]: def decode(row: Row): A def (`row: Row`) decodeRow: A = decode(row) ``` --- ## Scala 3 extension methods ```scala trait RowDecoder[A]: def decode(row: Row): A def (row: Row) `decodeRow`: A = decode(row) ``` --- ## Scala 3 extension methods ```scala trait RowDecoder[A]: def decode(row: Row): A def (row: Row) decodeRow: `A` = decode(row) ``` --- ## Scala 3 extension methods ```scala trait RowDecoder[A]: def decode(row: Row): A def (row: Row) decodeRow: A = `decode(row)` ``` --- ## Key takeaways -- * Type classes have a fair amount of dedicated syntax. -- * They're about to get more. --- class: center, middle # In closing --- ## If you only remember 1 slide... -- Type classes are: -- * a clever use of parametric polymorphism and implicit resolution. -- * convenient syntactic sugar. -- * great at getting the compiler to write code so that you don't have to. --- class: center, middle # Questions? Nicolas Rinaudo • [@NicolasRinaudo] • [Besedo] [@NicolasRinaudo]:https://twitter.com/NicolasRinaudo [Besedo]:https://twitter.com/besedo_official --- class: center, middle # But Haskell though... --- ## Global uniqueness ```haskell module Lib where data Boolean = T | F deriving (Eq, Show) ``` --- ## Global uniqueness ```haskell module Lib where data `Boolean = T | F` deriving (Eq, Show) ``` --- ## Global uniqueness ```haskell module Impl1 where import Data.Set import Lib instance Ord Boolean where compare T T = EQ compare T F = LT compare F T = GT compare F F = EQ ins :: Boolean -> Set Boolean -> Set Boolean ins = insert ``` --- ## Global uniqueness ```haskell *module Impl1 where import Data.Set import Lib instance Ord Boolean where compare T T = EQ compare T F = LT compare F T = GT compare F F = EQ ins :: Boolean -> Set Boolean -> Set Boolean ins = insert ``` --- ## Global uniqueness ```haskell module Impl1 where import Data.Set import Lib *instance Ord Boolean where * compare T T = EQ * compare T F = LT * compare F T = GT * compare F F = EQ ins :: Boolean -> Set Boolean -> Set Boolean ins = insert ``` --- ## Global uniqueness ```haskell module Impl1 where import Data.Set import Lib instance Ord Boolean where compare T T = EQ compare T F = LT compare F T = GT compare F F = EQ *ins :: Boolean -> Set Boolean -> Set Boolean *ins = insert ``` --- ## Global uniqueness ```haskell module Impl2 where import Data.Set import Lib instance Ord Boolean where compare T T = EQ compare T F = GT compare F T = LT compare F F = EQ ins' :: Boolean -> Set Boolean -> Set Boolean ins' = insert ``` --- ## Global uniqueness ```haskell *module Impl2 where import Data.Set import Lib instance Ord Boolean where compare T T = EQ compare T F = GT compare F T = LT compare F F = EQ ins' :: Boolean -> Set Boolean -> Set Boolean ins' = insert ``` --- ## Global uniqueness ```haskell module Impl2 where import Data.Set import Lib instance Ord Boolean where compare T T = EQ * compare T F = GT * compare F T = LT compare F F = EQ ins' :: Boolean -> Set Boolean -> Set Boolean ins' = insert ``` --- ## Global uniqueness ```haskell module Impl2 where import Data.Set import Lib instance Ord Boolean where compare T T = EQ compare T F = GT compare F T = LT compare F F = EQ *ins' :: Boolean -> Set Boolean -> Set Boolean *ins' = insert ``` --- ## Global uniqueness ```haskell module Main where import Data.Set import Lib import Impl1 import Impl2 test :: Set Boolean test = ins' T $ ins T $ ins F $ empty main :: IO () main = print test ``` --- ## Global uniqueness ```haskell module Main where import Data.Set import Lib *import Impl1 *import Impl2 test :: Set Boolean test = ins' T $ ins T $ ins F $ empty main :: IO () main = print test ``` --- ## Global uniqueness ```haskell module Main where import Data.Set import Lib import Impl1 import Impl2 *test :: Set Boolean *test = ins' T $ ins T $ ins F $ empty main :: IO () main = print test ``` --- ## Global uniqueness ```haskell module Main where import Data.Set import Lib import Impl1 import Impl2 test :: Set Boolean test = `ins' T` $ ins T $ ins F $ empty main :: IO () main = print test ``` --- ## Global uniqueness ```haskell module Main where import Data.Set import Lib import Impl1 import Impl2 test :: Set Boolean test = ins' T $ `ins T` $ ins F $ empty main :: IO () main = print test ``` --- ## Global uniqueness ```haskell module Main where import Data.Set import Lib import Impl1 import Impl2 test :: Set Boolean test = ins' T $ ins T $ ins F $ empty main :: IO () *main = print test ``` --- ## ~~Global uniqueness~~ ```haskell -- wtf Haskell I trusted you fromList [T,F,T] ``` --- ## ~~Global uniqueness~~ ```haskell -- wtf Haskell I trusted you fromList [`T`,F,`T`] ``` --- class: center, middle # Questions? Nicolas Rinaudo • [@NicolasRinaudo] • [Besedo] [@NicolasRinaudo]:https://twitter.com/NicolasRinaudo [Besedo]:https://twitter.com/besedo_official