class: center, middle # Typeclass 101: ad hoc polymorphism in scala Julien Truffaut • `@JulienTruffaut` ``` ``` .center[![sponsors](footer.png)] ??? - notes here --- # Installation ```shell git clone https://github.com/julien-truffaut/Typeclass.git cd Typeclass sbt clean answer/test ``` .center[![tests-output](scalaprop.png)] --- # Project setup Repository is on github `julien-truffaut/Typeclass` * `scala` (typelevel) 2.11.8 * `tut` for documentation * `scalaprops` for testing * 3 modules: * `slides` * `answer` * `exercice` ??? - typelevel scala to get miles fix for SI-2712 and not to talk about Unapply - it will not be needed once 2.12 or 2.11.9 is released -- ```scala 2 + 2 // res0: Int = 4 ``` ```scala scala> "Hello World".foo
:13: error: value foo is not a member of String "Hello World".foo ^ ``` ??? - all the code in the slides are compiled using tut - all imports are here, all error messages come from scalac --- # Ad hoc polymorphism? ```scala def plus(a1: Int, a2: Int): Int = ??? def plus(a1: Long, a2: Long): Long = ??? def plus[A](a1: A, a2: A): A = ??? ``` ```scala 1 + 2 // res3: Int = 3 3.2 + 4.0 // res4: Double = 7.2 ``` ## Paramertric polymorhism / Generics ```scala def headOption[A](xs: List[A]): Option[A] = ??? ``` ??? - overloading a function --- # Polymorphism example ```scala trait Input case class Text(label: String, maxLength: Int) extends Input case class Select(label: String, options: List[String]) extends Input ``` ```scala def setLabel[A](a: A, s: String): A = ??? ``` ??? - `A` in `setLabel` need some constraints --- # Subtyping ```scala trait Input { def setLabel(s: String): Input } case class Text(label: String, maxLength: Int) extends Input { def setLabel(s: String): Input = copy(label = s) } case class Select(label: String, options: List[String]) extends Input { def setLabel(s: String): Input = copy(label = s) } val text = Text("What is your name?", maxLength = 20) val select = Select("Favorite language?", List("scala", "haskell", "purescript")) ``` ```scala text.setLabel("foo") // res10: Input = Text(foo,20) select.setLabel("foo") // res11: Input = Select(foo,List(scala, haskell, purescript)) ``` --- # Subtyping ```scala trait Input { def setLabel(s: String): Input } case class Text(label: String, maxLength: Int) extends Input { def setLabel(s: String): Input = copy(label = s) } case class Select(label: String, options: List[String]) extends Input { def setLabel(s: String): Input = copy(label = s) } ``` -- ```scala scala> def setLabel[A <: Input](a: A, s: String): A = a.setLabel(s)
:13: error: type mismatch; found : Input required: A def setLabel[A <: Input](a: A, s: String): A = a.setLabel(s) ^ ``` --- # F-Bounded ```scala trait Input[A <: Input[A]] { def setLabel(s: String): A } case class Text(label: String, maxLength: Int) extends Input[Text] { def setLabel(s: String): Text = copy(label = s) } case class Select(label: String, options: List[String]) extends Input[Select] { def setLabel(s: String): Select = copy(label = s) } def setLabel[A <: Input[A]](a: A, s: String): A = a.setLabel(s) ``` ??? - this technique is called F-bounded polymorphism -- ```scala case class Radio(label: String, options: List[String]) extends Input[Text] { def setLabel(s: String): Text = Text(s, 10) // WTF } ``` --- # Self type ```scala trait Input[A <: Input[A]] { self: A => def setLabel(s: String): A } case class Text(label: String, maxLength: Int) extends Input[Text] { def setLabel(s: String): Text = copy(label = s) } case class Select(label: String, options: List[String]) extends Input[Select] { def setLabel(s: String): Select = copy(label = s) } def setLabel[A <: Input[A]](a: A, s: String): A = a.setLabel(s) ``` -- ```scala scala> case class Radio(label: String, options: List[String]) extends Input[Text] { | def setLabel(s: String): Text = Text(s, 10) | }
:15: error: illegal inheritance; self-type Radio does not conform to Input[Text]'s selftype Text case class Radio(label: String, options: List[String]) extends Input[Text] { ^ ``` --- # Self type ```scala abstract class Radio(labe: String, options: List[String]) extends Input[Radio] case class YesNo(label: String) extends Radio(label, List("Yes", "No")) { def setLabel(s: String): YesNo = copy(label = s) } case class Color(label: String) extends Radio(label, List("Red", "Blue", "Yellow")) { def setLabel(s: String): YesNo = YesNo(label = s) // WTF } ``` ??? - `YesNo` and `Color` are both an `Input[Radio]` - at this point, I give up with interitance --- class: center, middle # Typeclasses in Scala ??? - typeclasses are not first class (as opposed to haskell or purescript) so impl varies - but it generally contains the following components --- # 1. Trait and companion object ```scala trait Label[A] { def getLabel(a: A): String def setLabel(a: A, s: String): A } ``` ??? - trait or abstract class - 1 or more type parameters -- ```scala object Label { def apply[A](implicit ev: Label[A]): Label[A] = ev } ``` `apply` provides syntactic sugar to summon an instance ```scala Label[A] == implicitly[Label[A]] ``` --- # 2. Instances ```scala implicit val labelText: Label[Text] = new Label[Text] { def getLabel(a: Text): String = a.label def setLabel(a: Text, s: String): Text = a.copy(label = s) } ``` Instances are generally located in: 1. companion object of the type (e.g. `Text`) or 2. companion object of the typeclass (e.g. `Label`) or 3. an object in a dedicated package (e.g. `typeclass.instances.int`) ```scala val text = Text("What is your name?", maxLength = 20) // text: Text = Text(What is your name?,20) implicitly[Label[Text]].getLabel(text) // res22: String = What is your name? Label[Text].getLabel(text) // res23: String = What is your name? Label[Text].setLabel(text, "foo") // res24: Text = Text(foo,20) ``` ??? - preferably 1) but doesn't work if type define somewhere else e.g. String - 2) would be the alternative in haskell --- # 3. Syntax ```scala implicit class LabelOps[A](a: A)(implicit ev: Label[A]){ def getLabel: String = ev.getLabel(a) def setLabel(s: String): A = ev.setLabel(a, s) } ``` Syntax classes are generally located in: 1. companion of the typeclass (e.g. `Label`) 2. an object in a syntax package (e.g. `typeclass.syntax.int`) ```scala Label[Text].getLabel(text) // res25: String = What is your name? text.getLabel // res26: String = What is your name? text.setLabel("foo") // res27: Text = Text(foo,20) ``` ??? - this pattern is often refer to pimp a class - typeclasses permit retroactive extension, i.e. extending types define somewhere else - implicit class is just syntax sugar for class + implicit def --- # 4. Laws ```scala import scalaprops.{Gen, Properties} import scalaprops.Properties.properties import scalaprops.Property import scalaprops.Property.forAll import scalaz.std.string._ case class LabelLaws[A](implicit A: Label[A]) { def getSet(implicit genA: Gen[A]): Property = forAll((a: A) => a.setLabel(a.getLabel) == a) def setGet(implicit genA: Gen[A], genString: Gen[String]): Property = forAll((a: A, s: String) => a.setLabel(s).getLabel == s) def all(implicit genA: Gen[A], genString: Gen[String]): Properties[String] = properties("Label")( ("getSet", getSet), ("setGet", setGet)) } ``` ??? - `Gen` is a typeclass to generate random value - `Property` is a test that generate random input and test invariants - `Properties` is just a set of `Property` - there are more freedom regarding location of laws, some people like to have it in the same file or same module than the typeclass but it requires to add `Scalacheck` or `scalaprop` to your main dependencies. Other people prefer to define them in a separate module or in test --- # 4. Laws ```scala import scalaprops.Scalaprops object TextTest extends Scalaprops { implicit val genString: Gen[String] = Gen.asciiString implicit val genText: Gen[Text] = for { l <- Gen.asciiString i <- Gen.choose(1, 100) } yield Text(l, i) val label = LabelLaws[Text].all } ``` ??? - `LabelLaws` requires a `Gen[String]` and `Gen[A]`, here `A == Text` --- # Typeclasses in scala 1. typeclasses are encoded using a `trait` with one or more type parameters 2. values of the typeclass (instances) are marked as `implicit` 3. syntax to extend a class with methods from the typeclass 4. laws to define invariants of the typeclass methods and generate tests ??? - Typeclasses are not a 1st class construct means they are derived from other constructs - encoding is not unique, here I will present the most common one used in (scalaz, cats, argonaut, circe, etc ...) --- # Semigroup ```scala trait Semigroup[A] { def combine(x: A, y: A): A } ``` 1. Implement `SemigroupOps` in `typeclass.syntax.semigroup` 2. Implement associative law in `SemigroupLaws`: `(a + b) + c == a + (b + c)` 3. Implement an instance of `Semigroup` for `String`, `Int`, `List`, `Option` in `typeclass.instances` run tests using: `sbt exercise/test` ??? - `Semigroup[Int]` is not unique, `+` and `*`. Solution: newtype Mult - same for `String`: `s1 + s2` or `s2 + s1` - what about `Option`? `First`, `Last` --- # Semigroup examples ```scala import typeclass.syntax.semigroup._ import typeclass.instances.int._ import typeclass.instances.option._ ``` ```scala 5.combine(2) // res30: Int = 7 Option(5).combine(Option(2)) // res31: Option[Int] = Some(7) ``` ```scala scala> "Hello".combine(" World")
:36: error: value combine is not a member of String "Hello".combine(" World") ^ ``` ```scala import typeclass.instances.string._ ``` ```scala "Hello".combine(" World") // res33: String = Hello World ``` --- class: center, middle # Implicits --- # Implicits: type dictionary ```scala implicit val i: Int = 2 implicit val l: Long = 3L ``` ```scala implicitly[Int] // res34: Int = 2 implicitly[Long] // res35: Long = 3 ``` -- ```scala implicit val i2: Int = 0 ``` ```scala scala> implicitly[Int]
:42: error: ambiguous implicit values: both value i of type => Int and value i2 of type => Int match expected type Int implicitly[Int] ^ ``` ??? - you can define several implicits of the same type but it will fail if you use implicit resolution --- # Implicit parameters ```scala def plus3[A](a1: A, a2: A, a3: A)(implicit ev: Semigroup[A]): A = ev.combine(ev.combine(a1, a2), a3) def plus3[A](a1: A, a2: A, a3: A)(implicit ev: Semigroup[A]): A = a1.combine(a2).combine(a3) def plus3[A: Semigroup](a1: A, a2: A, a3: A): A = a1.combine(a2).combine(a3) ``` ```scala plus3(1, 2, 3) // res39: Int = 6 ``` ??? - scope: 1. Implicits defined in current scope 2. Explicit imports 3. Companion object of source type e.g. Option for Semigroup[Option] 4. Companion object of type class e.g. Semigroup 5. rules are in fact more complicated, see Where does Scala look for implicits? --- # Implicit conversion ```scala case class MyString(s: String){ def isPalindrome: Boolean = s == s.reverse } implicit def stringToMyString(s: String): MyString = new MyString(s) ``` ```scala implicit class MyString2(s: String){ def shout: String = s.map(_.toUpper) } ``` ```scala "hello".isPalindrome // res41: Boolean = false "kayak".isPalindrome // res42: Boolean = true "lalalala".shout // res43: String = LALALALA ``` ??? - essential for syntax --- # Instance uniqueness ```scala implicit val additiveLong: Semigroup[Long] = new Semigroup[Long]{ def combine(x: Long, y: Long): Long = x + y } implicit val multiplicativeLong: Semigroup[Long] = new Semigroup[Long]{ def combine(x: Long, y: Long): Long = x * y } ``` ```scala scala> implicitly[Semigroup[Long]]
:50: error: ambiguous implicit values: both value additiveLong of type => typeclass.Semigroup[Long] and value multiplicativeLong of type => typeclass.Semigroup[Long] match expected type typeclass.Semigroup[Long] implicitly[Semigroup[Long]] ^ ``` --- # Mult newtype ```scala case class Mult(value: Int) implicit val multSemigroup: Semigroup[Mult] = new Semigroup[Mult]{ def combine(x: Mult, y: Mult): Mult = Mult(x.value * y.value) } ``` ```scala Mult(2).combine(Mult(5)) // res47: Mult = Mult(10) 2.combine(5) // res48: Int = 7 ``` --- # First - Last newtype ```scala case class First[A](value: Option[A]) case class Last[A](value: Option[A]) ``` ```scala Option(1).combine(Option(2)) // res49: Option[Int] = Some(3) First(Option(1)).combine(First(Option(2))) // res50: typeclass.data.First[Int] = First(Some(1)) Last(Option(1)).combine(Last(Option(2))) // res51: typeclass.data.Last[Int] = Last(Some(2)) First(Option.empty[Int]).combine(First(Some(2))) // res52: typeclass.data.First[Int] = First(Some(2)) ``` 1. Implement a `Semigroup` instance for `First`, `Last` in `typeclass.data` ??? - for your own data type, instances are in the companion object => no need of extra import --- # Monoid ```scala trait Monoid[A] extends Semigroup[A] { def empty: A def isEmpty(a: A): Boolean = ??? def ifEmpty[B](a: A)(t: => B)(f: => B): B = ??? } ``` Such as ```scala combine(a, empty) == a combine(empty, a) == a ``` ??? - all `Monoid` are `Semigroup` - `empty` is defined to be the `zero` of `combine` --- # Monoid examples ```scala import typeclass.syntax.monoid._ import typeclass.instances.list._ ``` ```scala List(1,2,3).ifEmpty("Hello")("World") // res53: String = World 0.ifEmpty(10)(5) // res54: Int = 10 Option.empty.isEmpty // res55: Boolean = true ``` ??? - `Monoid` syntax allows to check for emptyness --- # Monoid examples ```scala def foldMap[A, B: Monoid](xs: List[A])(f: A => B): B = xs.map(f).foldLeft(Monoid[B].empty)(_.combine(_)) ``` ```scala foldMap(List(1,2,3,4))(identity) // res0: Int = 10 foldMap(List(1,2,3,4))(Mult(_)) // res1: typeclass.data.Mult = Mult(24) foldMap(List(1,2,3,4))(i => First(Option(i))) // res2: typeclass.data.First[Int] = First(Some(1)) foldMap(List(1,2,3,4))(i => Last(Option(i))) // res3: typeclass.data.Last[Int] = Last(Some(4)) ``` ??? - `Monoid` are generally used with `fold` see typeclass `Foldable` - `First` and `Last` provide an elegant way to implement `headOption` and `lastOption` - `Min`, `Max` --- # Monoid ```scala trait Monoid[A] extends Semigroup[A] { def empty: A def isEmpty(a: A): Boolean = ??? def ifEmpty[B](a: A)(t: => B)(f: => B): B = ??? } ``` 1. Derive `isEmpty` and `ifEmpty` from `empty` 2. Write `MonoidOps` in `typeclass.syntax.monoid` 3. Implement left identity law in `MonoidLaws`: `empty.combine(a) == a` 4. Implement right identity law in `MonoidLaws`: `a.combine(empty) == a` 5. Implement an instance of `Monoid` for `String`, `Int`, `List`, `Option` in `typeclass.instances` 6. Implement an instance of `Monoid` for `Mult`, `First`, `Last` in `typeclass.data` --- # NonEmptyList ```scala case class NonEmptyList[A](head: A, tail: List[A]) implicit def nelMonoid[A]: Monoid[NonEmptyList[A]] = new Monoid[NonEmptyList[A]]{ def combine(xs: NonEmptyList[A], ys: NonEmptyList[A]): NonEmptyList[A] = ??? def empty: NonEmptyList[A] = ??? } ``` 1. Try to implement an instance of `Monoid` for `NonEmptyList` ??? - `Monoid[NonEmptyList]` is impossible, there is no empty to pass right / left identity --- # Typeclass for higher kinded type ```scala scala> :k 1 scala.Int's kind is A scala> :k String java.lang.String's kind is A scala> :k Option scala.Option's kind is F[+A] scala> :k scala.Function1 scala.Function1's kind is F[-A1,+A2] ``` -- ```scala trait Semigroup[A]{} trait Functor[F[_]]{} new Semigroup[Int]{} new Functor[List]{} ``` ```scala scala> new Functor[Int]{}
:28: error: Int takes no type parameters, expected: one new Functor[Int]{} ^ ``` --- # Functor Exercices ```scala trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] def void[A](fa: F[A]): F[Unit] = ??? def as[A, B](fa: F[A], b: B): F[B] = ??? def lift[A, B](f: A => B): F[A] => F[B] = ??? } ``` 1. Implement `void`, `as` and `lift` from `map` 2. Write `FunctorOps` in `typeclass.syntax.functor` 3. Implement map identity law in `FunctorLaws`: `fa.map(id) == fa` 4. Implement map fusion law in `FunctorLaws`: `fa.map(f).map(g) == fa.map(g . f)` 5. Implement an instance of `Functor` for `List`, `Option` in `typeclass.instances` 6. Implement an instance of `Functor` for `NonEmptyList` in `typeclass.data` ??? - for `Option` try not to reuse `map` --- # Validation ```scala sealed trait Validation[E, A] case class Success[E, A](value: A) extends Validation[E, A] case class Failure[E, A](value: E) extends Validation[E, A] implicit def validationFunctor[E]: Functor[({type λ[a] = Validation[E, a]})#λ] = new Functor[({type λ[a] = Validation[E, a]})#λ]{ def map[A, B](fa: Validation[E, A])(f: A => B): Validation[E, B] = ??? } // or with kind-projector implicit def validationFunctor[E]: Functor[Validation[E, ?]] = new Functor[Validation[E, ?]]{ def map[A, B](fa: Validation[E, A])(f: A => B): Validation[E, B] = ??? } ``` 1. Implement an instance of `Functor` for `Validation` ??? - ? comes from kind projector --- # Applicative ```scala trait Applicative[F[_]] extends Functor[F]{ def pure[A](a: A): F[A] def ap[A, B](fab: F[A => B], fa: F[A]): F[B] def map[A, B](fa: F[A])(f: A => B): F[B] = ??? def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] = ??? def *>[A, B](fa: F[A], fb: F[B]): F[B] = ??? def <*[A, B](fa: F[A], fb: F[B]): F[A] = ??? def forever[A](fa: F[A]): F[A] = ??? def tuple2[A, B](fa: F[A], fb: F[B]): F[(A, B)] = ??? def lift2[A, B, C](f: (A, B) => C): (F[A], F[B]) => F[C] = ??? } ``` ??? - `Applicative` is a `Functor` - `Applicative` has two primitive: `pure` and `ap` - `ap` looks fairly complited because of `F[A => B]`, in essence `ap` and `map2` are the same - `Applicative` has a default implementation of `map` from `Functor` --- # Applicative examples ```scala import typeclass.syntax.applicative._ import typeclass.instances.list._ val xs = List(1,5,10) val ys = List(2,4) val fs = List[Int => Int](_ + 1, _ * 2, _ - 1) ``` ```scala fs.ap(xs) // res14: List[Int] = List(2, 6, 11, 2, 10, 20, 0, 4, 9) xs.map2(ys)(_ + _) // res15: List[Int] = List(3, 5, 7, 9, 12, 14) xs.tuple2(ys) // res16: List[(Int, Int)] = List((1,2), (1,4), (5,2), (5,4), (10,2), (10,4)) ``` --- # Applicative examples ```scala import typeclass.data.{Validation, NonEmptyList} import typeclass.data.Validation.{successNel, failureNel} import typeclass.syntax.applicative._ type ValidationNel[E, A] = Validation[NonEmptyList[E], A] case class Person(name: String, age: Int) def positive(n: Int): ValidationNel[String, Int] = if(n <= 0) failureNel(s"$n is not positive") else successNel(n) def capitalise(s: String): ValidationNel[String, String] = if(s != s.capitalize) failureNel(s"$s is not capitalised") else successNel(s) def person(name: String, age: Int): ValidationNel[String, Person] = capitalise(name).map2(positive(age))(Person(_, _)) ``` ```scala person("John", 24) // res21: ValidationNel[String,Person] = Success(Person(John,24)) person("john", 24) // res22: ValidationNel[String,Person] = Failure(Nel(john is not capitalised)) person("john", -2) // res23: ValidationNel[String,Person] = Failure(Nel(john is not capitalised, -2 is not positive)) ``` --- # Applicative exercices ```scala trait Applicative[F[_]] extends Functor[F]{ def pure[A](a: A): F[A] def ap[A, B](fab: F[A => B], fa: F[A]): F[B] def map[A, B](fa: F[A])(f: A => B): F[B] = ??? def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] = ??? def *>[A, B](fa: F[A], fb: F[B]): F[B] = ??? def <*[A, B](fa: F[A], fb: F[B]): F[A] = ??? def forever[A](fa: F[A]): F[A] = ??? def tuple2[A, B](fa: F[A], fb: F[B]): F[(A, B)] = ??? def lift2[A, B, C](f: (A, B) => C): (F[A], F[B]) => F[C] = ??? } ``` 1. Implement derived `Applicative` methods: `map`, `map2`, ... 2. Implement `ApplicativeOps` in `typeclass.syntax.functor` 3. Implement lift function law in `ApplicativeLaws`: `ap(pure f, pure a) == pure f(a)` 4. Implement ap id law in `ApplicativeLaws`: `ap(pure id, fa) == fa` 5. Implement map consistency law in `ApplicativeLaws` 6. Implement an instance of `Applicative` for `List`, `Option` in `typeclass.instances` 7. Implement an instance of `Applicative` for `NonEmptyList` and `Validation` in `typeclass.data` --- # ZipStream ```scala import typeclass.data.ZipStream val xs = ZipStream(1,5,10) val ys = ZipStream(2,4) val fs = ZipStream((_: Int) + 1, (_: Int) * 2, (_: Int) - 1) ``` ```scala fs.ap(xs) // res25: typeclass.data.ZipStream[Int] = ZipStream(2, 10, 9) xs.map2(ys)(_ + _) // res26: typeclass.data.ZipStream[Int] = ZipStream(3, 9) xs.tuple2(ys) // res27: typeclass.data.ZipStream[(Int, Int)] = ZipStream((1,2), (5,4)) ``` 1. Create `ZipStream` with such `Applicative` instance ??? - combine == zip but what is pure? --- # Typeclasses in the wild: Scalaprops Gen ```scala import scalaprops.{Gen, Property} ``` ```scala Gen[Int].sample() // res28: Int = -1 Gen[Int].samples(10) // res29: List[Int] = List(-1, 2147483646, -1579004998, 2147483647, -1944672731, 809094426, -1177512687, 2084672536, 609397212, 1811450929, -361913170, 1551745920, -283496851, -1, -2147483647, 1812852786, 735241234, -2147483647, 1359573808, -547914046, 640439652, 1, 1361931891, 147944788, 540721923, 902841100, -879609714, 802611720, 2103522059, 1913778154, -1519074049, -1263689967, -1481342794, -1529176048, 1958646067, -971018538, -529322872, 76338300, -769482973, -468566954, -174842015, 595387438, 988512770, -817183891, 1427854500, 1045931779, -2147483647, 1503168825, -650825878, -193198051, 2147483647, 2147483647, -726585505, -1375163528, -1057725500, 893645500, 1736062366, 0, -2147483647, -283421978, 663307952, 2016017442, 51119001, 1668894615, -311415874, 1336658413, 2147483647, -17094... ``` ```scala def forAll[A1: Gen](f: A1 => Boolean): Property = ??? def forAll[A1: Gen, A2: Gen](f: (A1, A2) => Boolean): Property = ??? ``` --- # Typeclasses in the wild: Scalaz/Cats Traverse ```scala import scalaz.concurrent.Task import scalaz.syntax.traverse._ import scalaz.std.list._ case class OrderId(value: String) case class Order(id: OrderId, value: Int) def getOrdersLargerThan(threshold: Int): Task[List[OrderId]] = ??? def fetchOrderById(id: OrderId): Task[Order] = ??? def hugeOrders: Task[List[Order]] = for { ids <- getOrdersLargerThan(1000) orders <- ids.traverseU(fetchOrderById) } yield orders ``` --- # Typeclasses in the wild: Argonaut Codec ```scala import argonaut.{Json, CodecJson} import argonaut.JsonIdentity._ case class Person(name: String, age: Int) implicit val codec: CodecJson[Person] = CodecJson.casecodec2(Person.apply, Person.unapply)("name", "age") val elise = Json.obj( "name" -> "Elise".asJson, "age" -> 32.asJson ) ``` ```scala Person("John", 26).asJson // res3: argonaut.Json = {"name":"John","age":26} elise.as[Person] // res4: argonaut.DecodeResult[Person] = DecodeResult(Right(Person(Elise,32))) ``` ??? - same for circe --- # Typeclasses in the wild: Shapeless Generic ```scala import shapeless._; import syntax.singleton._; import record._ case class Person(name: String, age: Int, hasSiblings: Boolean) val personGen = LabelledGeneric[Person] ``` ```scala val john = Person("John", 23, hasSiblings = false) // john: Person = Person(John,23,false) val record = personGen.to(john) // record: personGen.Repr = John :: 23 :: false :: HNil record('age) // res1: Int = 23 personGen.from(record.updateWith('age)(_ + 10)) // res2: Person = Person(John,33,false) ``` --- # Reduce boiler plate with simulacrum ```scala import simulacrum._ @typeclass trait Semigroup[A]{ @op("|+|", alias = true) def combine(x: A, y: A): A } implicit val stringSemigroup: Semigroup[String] = new Semigroup[String] { def combine(x: String, y: String): String = x + y } ``` ```scala import Semigroup.ops._ ``` ```scala Semigroup[String] // res5: Semigroup[String] = $anon$1@50d132af "Hello ".combine("World") // res6: String = Hello World "Hello " |+| "World" // res7: String = Hello World ``` --- # Other typeclass encoding ```scala abstract class Semigroup[A]{ def combine(x: A, y: A): A } abstract class Monoid[A]{ def semigroup: Semigroup[A] def empty: A } implicit val stringSemigroup: Semigroup[String] = new Semigroup[String]{ def combine(x: String, y: String): String = x + y } implicit val stringMonoid: Monoid[String] = new Monoid[String]{ val semigroup: Semigroup[String] = implicitly def empty: String = "" } ``` ??? - composition vs inheritance - proposed by alois cochard in scato and scalaz, maybe in cats --- class: center, middle # Thanks! Code and slides at `julien-truffaut/Typeclass` on GitHub ## Questions? --- # References - [remark.js](https://github.com/gnab/remark) - [tut](https://github.com/tpolecat/tut) - [presentation.g8](https://github.com/julien-truffaut/presentation.g8) - [kind-projector](https://github.com/non/kind-projector) - [scalaz](https://github.com/scalaz/scalaz) [cats](https://github.com/typelevel/cats) - [argonaut](https://github.com/argonaut-io/argonaut) [circe](https://github.com/travisbrown/circe) - [scato](https://github.com/aloiscochard/scato) - [Returning the Current Type in Scala](https://tpolecat.github.io/2015/04/29/f-bounds.html) by Rob Norris - [Type Classes as Objects and Implicits](http://ropas.snu.ac.kr/~bruno/papers/TypeClasses.pdf) by Oliveira, Bruno; Adriaan Moors; Martin Odersky (2010) - [Where does Scala look for implicits?](http://docs.scala-lang.org/tutorials/FAQ/finding-implicits.html)