few simple-type-tricks in scala
TRANSCRIPT
Scala: few simple type tricks for mortals
Ruslan Shevchenko <[email protected]>
Hieronymus Bosch “A visual guide to the Scala language” oil on oak panels, 1490-1510
// http://classicprogrammerpaintings.tumblr.com/
scala types
Structured
Classical OO
Algebraic
Generic
Aliasing
5D SPACE
Alternatives
• Classical OO / Algebraic (case classes)
• Generic / Type Alias
• Structured / Nominal
// decrease problem space
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
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, ….
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 = ….. )
Type Aliases are path-dependedval a = retrieveUser(“a1”) val b = retrieveUser(“b1”)
a.address and b.address - different types
Aux patternobject 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
When to prefer generic ?• Containers.
• Compositions.
• Internal machinery.
• (part of more complex process)
• Dotty: unification of type aliases and type parameters.
{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
{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
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)
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
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(….)
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
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!”
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 }
type arithmeticstrait 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] { …… }
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
Questions ?
• Thanks for attention.