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)
}
}