The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
API design for type classes and dependent types
1. API design: using type classes
and dependent types
Ben Lever
@bmlever
ScalaSyd Episode #6
11 July 2012
2. What I want
traitComp[A] { ... }
Specifies the
“computation” of a
value of type ‘A’
val ii: Comp[Int] = ...
vali: Int = exec(ii)
“Execute” the
computation
specified by ‘ii’.
3. What would be cool
val ii: Comp[Int] = ...
valss: Comp[String] = ...
val (i: Int, s: String) = exec(ii, ss)
“Execute” multiple
computations of
different types, then
return “embedded”
types as a tuple.
4. More generally
valaa: Comp[A] = ...
val bb: Comp[B] = ...
valcc: Comp[C] = ...
valdd: Comp[D] = ...
val (a: A, b: B, c: C, d: D) = exec(aa, bb, cc, dd)
Ability to “execute” many
computations
simultaneously and return
all “embedded” values
together retaining types.
5. But wait, there’s more
traitIOComp[A] { ... }
Specifies the “IO
computation” of a
value of type ‘A’
val ii: IOComp[Int] = ...
Err[A]
vali: Err[Int] = exec(ii) ||
Either[String, A])
Either return the “Execute” the IO
computed value computation
or an error string specified by ‘ii’.
6. And of course
val ii: IOComp[Int] = ...
valss: IOComp[String] = ...
val (i: Err[Int],
s: Err[String]) = exec(ii, ss)
“Execute” multiple IO
computations of
different types, then
return “embedded”
types as a tuple.
7. Finally
valaa: Comp[A] = ...
val bb: IOComp[B] = ...
valcc: Comp[C] = ...
valdd: IOComp[D] = ...
val (a: A, b: Err[B], c: C, d: Err[D]) =
exec(aa, bb, cc, dd)
“Execute” a mixture
of normal and IO
computations.
8. Teaser - the final API
defexec[R](v: R)(implicit runner: Runner[R]): runner.Out
9. My inspiration
“Couldn’t you use HLists/KLists for this?”
Miles Sabin’s Shapeless
provided much
direction and inspirations
But … I’m not going to talk about Shapeless
12. Trick #2: type classes
Get the “size” of an object
size(3) // 3
size(“hello”)// 5
size(false)// 1
...
13. Size type class
traitSize[T] {
defapply(in: T): Int Type class
}
object Size {
defsize[S](v: S)(implicits: Size[S]): Int = s(v)
implicit defIntSize = newSize[Int] {
defapply(in: Int): Int = in
}
implicit defBoolSize = newSize[Boolean] {
defapply(in: Boolean): Int = 1
Type class
instances
}
implicit defStringSize = newSize[String] {
defapply(in: String): Int = in.length
}
}
14. Under the hood
Implicit objects inserted by compiler
size(3)(IntSize)// 3
size(“hello”)(StringSize)// 5
size(false)(BoolSize)// 1
...
16. Runner type class
traitRunner[In, Out] {
defapply(in: In): Out
}
object Runner {
defsize[I, O](v: I)(implicitr: Runner[I, O]): O = r(v)
implicit def Tup1[T1](implicit s1: Size[T1]) =
new Runner[T1, Int] {
defapply(in: T1): Int = s1(in) Referencing
} ‘Size’
implicit def Tup2[T1,T2](implicit s1: Size[T1], s2: Size[T2]) =
new Runner[(T1,T2), (Int,Int)] {
defapply(in: (T1,T2)): (Int,Int) = (s1(in._1), s2(in._2))
}
implicit def Tup3[T1,T2,T3](implicit s1: Size[T1], ...
17. Under the hood
Implicit object inserted by compiler
size(3, “hello”,false)
(Tup3
IntSize,
StringSize,
BoolSize))
18. Aside: type class sugar
defsize[S](v: S)(implicitsizer: Size[S]): Int
is equivalent to
defsize[S : Size](v: S) : Int
Type class
constraint
19. ‘size’ is easy
Runner instances know output types are always Ints
Returns Int
size(3, “hello”,false)// (3,5,1)
Returns Int Returns Int
20. ‘exec’ is harder
Output types are always different
Returns Returns
Err[B] Err[D]
exec(aa, bb, cc, dd)
Returns A Returns C
21. Exec type class
traitExec[In, Out] {
defapply(in: In): Out
}
object Exec {
implicit defcompExec[A] = newExec[Comp[A], A] {
defapply(in: Comp[A]): A = execute(in)
}
implicit defioCompExec[A] = newExec[IOComp[A], Err[A]] {
defapply(in: IOComp[A]): Err[A] = ioExecute(in)
}
}
22. Updated Runner type class
Runner return type is dependent on Exec return type
traitRunner[In, Out] {
defapply(in: In): Out
}
object Runner {
defexec[I,O](v: I)(implicitr: Runner[I,O]): O = r(v)
implicit def Tup1[T1](implicit ex1: Exec[T1,?]) =
new Runner[T1, ?] {
defapply(in: T1): ? = ex1(in)
} Needs to be
... dependent on ex1
24. Trick #3: Dependent types
Dependent types are types where the realization
of the type created is actually dependent on
the values being provided.
(SCALABOUND)
25. Type parameters vs members
traitExec[In, Out] { Type
parameter
defapply(in: In): Out
}
is equivalent to
traitExec[In] {
typeOut Type
member
defapply(in: In): Out
}
26. Exec using type members
traitExec[In] {
typeOut
defapply(in: In): Out
}
object Exec {
implicit defcompExec[A] = newExec[Comp[A]] {
typeOut = A
defapply(in: Comp[A]): A = execute(in)
}
implicit defioCompExec[A] = newExec[IOComp[A]] {
typeOut = Err[A]
defapply(in: IOComp[A]): Err[A] = ioExecute(in)
}
}
27. Using dependent method types
traitRunner[In] {
typeOut
defapply(in: In): Out Return type is
dependent on
}
‘r’. Access type
as a member.
object Runner {
defexec[R](v: R)(implicitr: Runner[R]): r.Out = r(v)
implicit def Tup1[T1](implicit ex1: Exec[T1]) =
new Runner[T1] {
typeOut = ex1.Out
defapply(in: T1): ex1.Out = ex1(in)
}
Return type is dependent on
... ex1’s return type
28. And again
implicit def Tup2[T1,T2](implicit ex1: Exec[T1],
ex2: Exec[T2]) =
new Runner[(T1,T2)] {
typeOut = (ex1.Out, ex2.Out)
defapply(in: (T1,T2)): Out = (ex1(in._1),
ex2(in._2))
}
...