##Shapeless
Type class derivation: applies to ADT(Alg Data Types). ADT is a sealed _empty_trait extended by case classes. Many advantages
Type class: trait indexed on a type T and provides some methods and defs on the type T. in the comp obj we define a def of the typed class for that type, and we can derive it through the implicitly
trait Eq[T] {
def eqv(x: T, y: T): Boolean
}
trait Animal...
object Animal {
implicit def eqAnimal: Eq[Animal]...
}
def foo[T](x: T, y: T)(implicit eqt: Eq[T]) {
eqt.eqv(x, y)
}
and the implicit class to provide operators:
implicit class EqOps[T](x: T)(implicit eqt: Eq[T]) {
def ===(y: T): Boolean = eqT.eqv(x, y)
}
How compiler looks for implicits: implicit[T]
: look for immediate scope, and if not look on the companion objects of any type mentioned in the type. e.g. implicit Foo[Bar] ==> looks for implicit defs in the c.o. or Foo and in the c.o. of Bar
we defined the === in terms of any equivalence of any type that might be used in the ADT definition
we want the generation of the implicit type classes to be generated somehow by compiler
##about ADT Sum/product definition of ADT. We want to exploit this definition to have everything inferred by the compiler, we want a generic definition without cats and dogs
##Shapeless
scala> val l = 23 :: "foo" :: true :: HNil
l: shapeless.::[Int,shapeless.::[String,shapeless.::[Boolean,shapeless.HNil]]] = 23 :: foo :: true :: HNil
hetherogeneous list: it preserves the type of components. See Ammonite repl to see how it can be represented the infix way
When we take the head, we know at compile time what is the type of that head
Tree[T] :: Tree[T] :: HNil
defines a product type of node!!
T :: HNil
defines a product type of the leaf
Leaf[T] :+: Node[T] :+: CNil
defines the sum type of the tree itself
-xloadImplicits ??? in the compiler to get information about implicit resolution.
Sum types: CNil: it is kind of nothing, it is not a nil, there is nothing at all. See Coproduct type in shapeless So if we have 2 animals being eq, animal can be either Cat or Dog
Shapeless' Generic
provides a way to transform any case class to the equivalent HList
, and go backwards
val gen = Generic[Cat]
gen.to(instanceOfCat) //---> HList representation of the Cat
Generic[T]
provides way to go from/to represenation vs of a ADT to an equivalent HList
or Coproduct
. Using this we can define a generic equivalence method on any ADT. A Coproduct
can be either the first alternative (Inl
), or any other alternative (Inr
) among the ones available in the Coproduct definition.
In case of a to
used on a Product type the representation is an HList, in case of a Coproduct type the representation is a Coproduct
, that can be then resolved in an Inl
(equivalent to a "first alternative of the coproduct") or Inr
(equivalent to "something else in the coproduct found"). Watch out for the toString
of Inl
and Inr
that hide the fact that we are talking about a coproduct, therefore int he REPL you might think that genAnimal.to(felix)
returns a Cat
, but instead it returns an Inl[Cat, ...]
scala> val genCat = Generic[Cat]
gen: shapeless.Generic[derivation.equal.Cat]{type Repr = shapeless.::[String,shapeless.::[Int,shapeless.HNil]]} = fresh$macro$3$1@1ee89f2
scala> val felix2 = Cat("Felix", 2)
felix2: derivation.equal.Cat = Cat(Felix,2)
scala> genCat.to(felix2)
res0: gen.Repr = Felix :: 2 :: HNil
scala> res0.head
res1: String = Felix
scala> res0.head :: (res0.tail.head + 1) :: HNil
res2: shapeless.::[String,shapeless.::[Int,shapeless.HNil]] = Felix :: 3 :: HNil
scala> genCat.from(res2)
res3: derivation.equal.Cat = Cat(Felix,3)
//and make a Cat to a Dog
scala> val genDog = Generic[Dog]
genDog: shapeless.Generic[derivation.equal.Dog]{type Repr = shapeless.::[String,shapeless.::[Int,shapeless.HNil]]} = fresh$macro$6$1@50b93e10
scala> genDog.from(genCat.to(felix2))
res4: derivation.equal.Dog = Dog(Felix,2)
scala> val genAnimal = Generic[Animal]
genAnimal: shapeless.Generic[derivation.equal.Animal]{type Repr = shapeless.:+:[derivation.equal.Cat,shapeless.:+:[derivation.equal.Dog,shapeless.CNil]]} = fresh$macro$9$1@6c616408
scala> genAnimal.to(felix)
res13: genAnimal.Repr = Cat(Felix,5)
//watch out: previous expression din't return a Cat, but an Inl[Cat]
scala> val Inl(c) = genAnimal.to(felix)
c: derivation.equal.Cat = Cat(Felix,5)
scala> val Inl(c) = genAnimal.to(tigger)
scala.MatchError: Dog(Tigger,2) (of class shapeless.Inr)
... 35 elided
scala> val Inr(c) = genAnimal.to(tigger)
c: shapeless.:+:[derivation.equal.Dog,shapeless.CNil] = Dog(Tigger,2)
The Generic object type is aware of the type it is representing. In the to() part the representation is unaware of the type. If we get the Generic of a top level, like Animal, we get a Coproduct, or a Sum type:
val genA = Generic[Animal]
genA.to(felix)
we use Gen.Aux
that lifts the type parameter as a type member in the class
Question: Why the base trait must be sealed? Because otherwise the compiler cannot resolve implicitly[Eq[Unsealed]]
##Applications Serialization/Codecs. Anything that comes naturally from the structure of they types, can be done through these generic programming structures.
why Lazy[]?
implicit def foo(implicit foo2: Foo):Foo = foo2
this implicit resolution would diverge, compiler boom and gives up
With recursive types like Tree we have this problems finding the implicit to be applied, therefore we must work around this problem. Using Lazy[] we produce a dictionary (variable) that exists only at compile time.
Advice: wrap into Lazy any type that we might figure having an implicitly resolution loop. But we promise the compiler that the loop will end at some point. If we don't keep the promise, loop @ compile time.
Functor: anything with a map is a functor.
Golden rule: put always implicit return value type!