4. Alternatives
• Classical OO / Algebraic (case classes)
• Generic / Type Alias
• Structured / Nominal
// decrease problem space
5. Alternatives
• Prefer specific style
• (typelevel: prefer Generic + ADT)
• (java++ : prefer OO + std. ADT)
• Try to set some rules.
• http://www.lihaoyi.com/post/StrategicScalaStylePrincipleofLeastPower.html
• least power
• most readability
6. Alternatives
• Classical OO / ADT
• state vs functionality.
OO: Object = State + Function
FP:
State = ADT, passive.
Object = Service over State
Non-ADT Object with state ?
rare, special, ….
7. Alternatives
• Generic/Type Alias
case class User[IdType,Address](
id: IdType,
firstName: String.
lastName: String
address: Address
)
case class User extends IdEntity(
id: IdType,
firstName: String,
lastName: String,
address: Address
) {
type Address = …..
)
8. Type Aliases are path-depended
val a = retrieveUser(“a1”)
val b = retrieveUser(“b1”)
a.address and b.address - different types
Aux pattern
object User
{
type Aux[I,A] = User {
type IdType = I
type Address = A
}
}
def method[Id,Type](user: User.Aux[Id,Type] ): X =
…………..
// original from shapeless
9. When to prefer generic ?
• Containers.
• Compositions.
• Internal machinery.
• (part of more complex process)
• Dotty: unification of type aliases and type
parameters.
10. {compile/run}-time dispatch
• run time dispatch — via java virtual dispatch
• depends from one object
• final object type can not be known at compile-time
• compile time dispatch — via implicit resolution
• depends from multiple objects
• final object types must be known at compile-time
11. {compile/run}-time dispatch
• run time dispatch — via java virtual dispatch
• depends from one object
• final object type can not be known at compile-time
• compile time dispatch — via implicit resolution
• depends from multiple objects
• final object types must be known at compile-time
Immutable data —> we know constructor
12. implicit resolution
• provide global rule in you world
• rule must be really global or special for you types
• good citizent in big project must be tolerant:
• do not force others to know something about you
implicits
• keep you implicits in your scope [companion obj, etc]
• problem: we want to have different behaviour for
different object state (known at compile time)
13. tagged typesobject tagged types {
trait Tagged[+V]
type @@[V,T] = V with Tagged[T]
implicit class WithTag[V](val v:V) extends AnyVal
{
def @@[T](t:T) = v.asInstanceOf[T]
def tag[T] = v.asInstanceOf[T]
}
}
// origin: was as scalaz, shapeless
14. tagged typesobject tagged types {
trait Tagged[+V]
type @@[V,T] = V with Tagged[T]
implicit class WithTag[V](val v:V) extends AnyVal
{
def @@[T](t:T) = v.asInstanceOf[T]
def tag[T] = v.asInstanceOf[T]
}
}
// origin: was as scalaz, shapeless
sealed trait UserLifecycle
trait New extends UserLifecycle
trait Existing extends UserLifecycle
case class User(….)
15. tagged typesobject tagged types {
trait Tagged[+V]
type @@[V,T] = V with Tagged[T]
implicit class WithTag[V](val v:V) extends AnyVal
{
def @@[T](t:T) = v.asInstanceOf[T]
def tag[T] = v.asInstanceOf[T]
}
}
// origin: was as scalaz, shapeless
case class Msg(v:String)
object User {
implicit def msgNew(u: User @@New:Msg = Msg(“Hi”)
implicit def msgExisting(u: User@@Existing):Msg = Msg(“Hi again!”)
}
sealed trait UserLifecycle
trait New extends UserLifecycle
trait Existing extends UserLifecycle
16. tagged typesobject tagged types {
trait Tagged[+V]
type @@[V,T] = V with Tagged[T]
implicit class WithTag[V](val v:V) extends AnyVal
{
def @@[T](t:T) = v.asInstanceOf[T]
def tag[T] = v.asInstanceOf[T]
}
}
// see: refinement
case class Msg(v:String)
object User {
implicit def msgNew(u: User @@New:Msg = Msg(“Hi”)
implicit def msgExisting(u: User@@Existing):Msg = Msg(“Hi again!”)
}
sealed trait UserLifecycle
trait New extends UserLifecycle
trait Existing extends UserLifecycle
class UserService
{
def create(cn: Info): User @@ New
def meet(u: User @@ New): User @@ Existing
}
val u = userService.create(cn)
printMsg(u)
> >> “Hi”
val u1 = userService.meet(u)
printMsg(u1)
>>> “Hi again!”
17. implicit evidence
• existing implicit evidence <=> existence of
property
object example {
def notCallMeWithA[T](t:T)(implicit evidence: Not[A,T])
trait Not[A,T]
implicit def n1[A,B](implicit evidence A <:< B) = Not.instance
implicit def n2[A,B](implicit evidence B <:< A) = Not.instance
implicit def n3[A] = Not.instance
}
18. type arithmetics
trait Nat {
type Current <: Nat
type Next <: Nat
}
type Zero {
type Current = Zero
type Next = Succ(Zero)
}
type Succ[X<:Nat]
{
type Current = Succ[X]
type Next = Succ[Succ[X]]
}
type _0 = Zero
type _1 = Succ[Zero]
type _2 = Succ[Succ[Zero]]
totally ineffective
shapeless contains effective implementation
class SizedList[X <: Nat]
{
……
}
19. Resources:
• shapeless (big, use with care)
• https://github.com/milessabin/shapeless
• http://mpilquist.github.io/blog/2015/04/22/intro-to-shapeless/
• type-level computations
• http://slick.typesafe.com/talks/scalaio2014/Type-Level_Computations.pdf