• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
API design: using type classes and dependent types
 

API design: using type classes and dependent types

on

  • 2,172 views

A walkthrough of some of the techniques I used when implementing a particular API with challenging type requirements.

A walkthrough of some of the techniques I used when implementing a particular API with challenging type requirements.

Statistics

Views

Total Views
2,172
Views on SlideShare
2,128
Embed Views
44

Actions

Likes
5
Downloads
0
Comments
2

2 Embeds 44

http://www.redditmedia.com 36
https://si0.twimg.com 8

Accessibility

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

12 of 2 previous next

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    API design: using type classes and dependent types API design: using type classes and dependent types Presentation Transcript

    • API design: using type classes and dependent types Ben Lever @bmlever ScalaSyd Episode #6 11 July 2012
    • What I wanttraitComp[A] { ... } Specifies the “computation” of a value of type ‘A’val ii: Comp[Int] = ...vali: Int = exec(ii) “Execute” the computation specified by ‘ii’.
    • What would be coolval 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.
    • More generallyvalaa: 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.
    • But wait, there’s moretraitIOComp[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’.
    • And of courseval 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.
    • Finallyvalaa: 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.
    • Teaser - the final APIdefexec[R](v: R)(implicit runner: Runner[R]): runner.Out
    • My inspiration “Couldn’t you use HLists/KLists for this?” Miles Sabin’s Shapeless provided much direction and inspirationsBut … I’m not going to talk about Shapeless 
    • The tricksdefexec[R](v: R)(implicit runner: Runner[R]): runner.Out Tuple Type Dependent sweetness classes types
    • Trick #1: tuple sweetnessdefecho[A](x: A) { prinln(x) }echo((“good”, 47, true))echo(“good”, 47, true) // equivalent
    • Trick #2: type classesGet the “size” of an objectsize(3) // 3size(“hello”)// 5size(false)// 1...
    • Size type classtraitSize[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 }}
    • Under the hoodImplicit objects inserted by compilersize(3)(IntSize)// 3size(“hello”)(StringSize)// 5size(false)(BoolSize)// 1...
    • Expanding the exampleNow, get the “size” of multiple objects at oncesize(3, “hello”,false)// (3,5,1)
    • Runner type classtraitRunner[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], ...
    • Under the hoodImplicit object inserted by compilersize(3, “hello”,false)(Tup3IntSize,StringSize,BoolSize))
    • Aside: type class sugardefsize[S](v: S)(implicitsizer: Size[S]): Intis equivalent todefsize[S : Size](v: S) : Int Type class constraint
    • ‘size’ is easyRunner instances know output types are always Ints Returns Intsize(3, “hello”,false)// (3,5,1) Returns Int Returns Int
    • ‘exec’ is harderOutput types are always different Returns Returns Err[B] Err[D]exec(aa, bb, cc, dd) Returns A Returns C
    • Exec type classtraitExec[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) }}
    • Updated Runner type classRunner return type is dependent on Exec return typetraitRunner[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
    • It gets worseimplicit def Tup2[T1,T2](implicit ex1: Exec[T1,?], ex2: Exec[T2,?]) =new Runner[(T1,T2),(?,?)] { defapply(in: (T1,T2)): (?,?) = (ex1(in._1), ex2(in._2)) } ...
    • Trick #3: Dependent typesDependent types are types where the realization of the type created is actually dependent on the values being provided. (SCALABOUND)
    • 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 }
    • Exec using type memberstraitExec[In] {typeOutdefapply(in: In): Out}object Exec {implicit defcompExec[A] = newExec[Comp[A]] {typeOut = Adefapply(in: Comp[A]): A = execute(in) }implicit defioCompExec[A] = newExec[IOComp[A]] {typeOut = Err[A]defapply(in: IOComp[A]): Err[A] = ioExecute(in) }}
    • Using dependent method typestraitRunner[In] {typeOutdefapply(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.Outdefapply(in: T1): ex1.Out = ex1(in) } Return type is dependent on ... ex1’s return type
    • And againimplicit 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)) } ...
    • Remember -YDependent method types are still experimental! (but “turned-on” in 2.10.x) -Ydependent-method-types