21a. Implicits KATA (Type classes) - RobertMakyla/scalaWiki GitHub Wiki

import org.scalatest.{FreeSpec, MustMatchers}
/**
  * Mamy obiekty typów: String, Int, List, Set
  * chcemy umiec dodawać obiekty typów wymienionych za pomocą cechy: dodaj()
  * chcemy dodać tą ceche do tych typów
  *
  * Na koncu napiszmy funkcję sum() ktora bierze wszystkie liste wszystkich 4 tych typów i potrafi je zsumować
  * bo każdy z 4 tych typów ma ceche dodawanie
  */
class TypeClassesKata extends FreeSpec with MustMatchers {

  //Tworzenie nowej funkcjonalności dla każdego typu

  trait Addytor[T] {
    def dodaj(a: T, b: T): T
    def elementNeutralny: T // potrzebne dla foldLeft
  }

  object AddytorStringa extends Addytor[String] {
    def dodaj(a: String, b: String) = a + b
    def elementNeutralny = ""
  }

  object AddytorInta extends Addytor[Int] {
    def dodaj(a: Int, b: Int) = a + b
    def elementNeutralny = 0
  }

  // do nowej funkcjonalności przy List i Set potrzebujemy zdeklarować Generic type: A
  // dlatego zamiast obiektu tworzymy nową funkcjonalność jako metodę
  def AddytorListy[A] = new Addytor[List[A]] {
    def dodaj(a: List[A], b: List[A]): List[A] = a ++ b
    def elementNeutralny: List[A] = Nil
  }

  def AddytorSeta[A] = new Addytor[Set[A]] {
    def dodaj(a: Set[A], b: Set[A]): Set[A] = a ++ b
    def elementNeutralny: Set[A] = Set.empty
  }

  // Udostepnianie nowej funkcjonalności w sposób implicit

  implicit val addytorStringa = AddytorStringa
  implicit val addytorInta = AddytorInta
  implicit def addytorListy[A]: Addytor[List[A]] = AddytorListy[A]
  implicit def addytorSeta[A]: Addytor[Set[A]] = AddytorSeta[A]

  // Sugar OPSy

  trait AddingOps[A] {
    def me: A
    def addytor: Addytor[A]
    def dodaj(a: A): A = addytor.dodaj(me, a)
  }

  implicit def toAddingOps[A](a: A)(implicit ia: Addytor[A]) = new AddingOps[A] {
    override def me: A = a
    override def addytor: Addytor[A] = ia
  }

  // albo jeszcze zgrabniej (za pomocą Context Bound)

  implicit def toAddytorOps[A: Addytor](a: A): AddingOps[A] = new AddingOps[A] {
    override def me: A = a
    override def addytor: Addytor[A] = implicitly[Addytor[A]]
  }

  // lub za pomocą klasy implicit

  implicit class MyAddytorOps[A: Addytor](a: A): AddingOps[A] extends AddingOps[A] {
    override def me: A = a
    override def addytor: Addytor[A] = implicitly[Addytor[A]]
  }


  "Typy: String, Int, List, Set powinny miec ceche: dodaj()" in {

    "aaa" dodaj "bbb" mustBe "aaabbb"
    1 dodaj 2 mustBe 3
    Set(1, 2, 3) dodaj Set(2, 3, 4, 5) mustBe Set(1, 2, 3, 4, 5)
    Set('a, 'b, 'c) dodaj Set('c, 'd) mustBe Set('a, 'b, 'c, 'd)
    List(1, 2, 3) dodaj List(2, 3, 4, 5) mustBe List(1, 2, 3, 2, 3, 4, 5)
    List('a, 'b, 'c) dodaj List('c, 'd) mustBe List('a, 'b, 'c, 'c, 'd)

  }

  def sum[A](ls: A*)(implicit addytor: Addytor[A]) = {
    ls.foldLeft(addytor.elementNeutralny)(_ dodaj _)
  }

  "funkcja sum() powinna dzialac" in {

    sum("Siala baba mak", " nie wiedziala jak") mustBe "Siala baba mak nie wiedziala jak"
    sum(1, 2, 3, 4) mustBe 10
    sum(List("siala", "baba", "mak"), List("i"), List("nie wiedziala jak")) mustBe List("siala", "baba", "mak", "i", "nie wiedziala jak")
    sum(Set('boom, 'srum), Set('boom, 'kaboom), Set('ciach, 'boom)) mustBe Set('boom, 'srum, 'boom, 'kaboom, 'ciach)
    sum(List(0, 1), List(1, 0, 0, 1), List(9, 9, 9)) mustBe List(0, 1, 1, 0, 0, 1, 9, 9, 9)
  }
  
}