Type Parameters vs Type Members

Arnold deVos @a4dev

The talk explores when to use type members vs. type parameters through a series of examples.


But first ..

scala Syd Festivus

Ritual airing of scala grievances.


trait

trait Key
val a, b, c = new Key {}
val m = (
  Map.empty
  .updated( a, "hi")
  .updated( b, "there"))
 
m.get(a) == Some("hi")
m.get(c) == None

trait with type parameter

trait Key[A]
val a = new Key[String] {}
val b = new Key[Int] {}
val m = (
  HMap.empty
  .add(a)("ballons")
  .add(b)(99))
 
m.get(a) == Some("balloons")
m.get(b) == Some(99)

The Essence of Dependent Object Types

Nada Amin, Samuel Grütter, Martin Odersky, Tiark Rompf, and Sandro Stucki

While hiking together in the French alps in 2013, Martin Odersky tried to explain to Phil Wadler why languages like Scala had foundations that were not directly related via the Curry-Howard isomorphism...


Exactly what Professor Wadler said is not recorded.

alt text


trait with type Members

trait Key { type A }
val a = new Key { type A = String }
val b = new Key { type A = Int }
val m = (
  HMap.empty
  .add(a)("balloons")
  .add(b)(99))
 
m.get(a) == Some("balloons")
m.get(b) == Some(99)

projection operator

With type parameters

def project(m0: HMapks: Key[_]*): HMap = ???

With type members

def project(m0: HMapks: Key*): HMap = { ... }

Can write the latter, not sure about the former.


Existential Types

alt text

Simone de Beauvoir and Jean-Paul Sartre


Lesson 1

use a type member if a parameter would usually be existential


rule expansion

trait Rule { type Value; ... }
def rulePf(k: Key)( pf: PartialFunction[HMapk.Value]): Rule
def applyRules(h: HMaprs: List[Rule]): HMap
val rules = List(
  rulePf(Attention)  { case Title(a) & Patient(p) => s"$a $p" },
  rulePf(Attention)  { case Patient(p) => p },
  rulePf(BatchCount) { case Method("nogap") => 1 },
  rulePf(GST)        { case FeeExGST(f) => f * 0.1 },
  rulePf(GST)        { case Amount(a) => a / 11.0 },
  rulePf(Amount)     { case FeeExGST(f) & GST(g) => f+},
  rulePf(FeeExGST)   { case Amount(a) & GST(g) => a-},
  rulePf(OpeningBalance) { case Amount(a) => a },
  rulePf(Balance)    { case OpeningBalance(a) & Paid(p) => a-}
)

Lesson 2

use a type member if the type's usage is (mostly) contained within the trait


reducers and transducers

type Context[+S]
 
trait Reducer[-A+S] {
  type State
  def init: State
  def apply(s: Statea: A): Context[State]
  def isReduced(s: State): Boolean
  def complete(s: State): S
}
def educe[XS]( xs: Iterator[X]f: Reducer[XS]): Context[S]
trait Transducer[+A-B] {
  def apply[S](fa: Reducer[AS]): Reducer[BS]
}

Lesson 3

declare types that should be exposed in parameters and types that should be hidden in members

Lesson 4

declare a type (or type constructor) that is widely used in a member in an enclosing scope


Encodings

What Parameter Member
nonvariant decl. trait T[X] {} trait T { type X }
nonvariant appl. T[A] T { type X = A }
covariant decl trait T[+X] {} trait T { type X }
covariant appl. T[A] T { type X <: A }
contravariant decl trait T[-X] {} trait T { type X }
contravariant appl. T[A] T { type X >: A }
existential T[_ <: A] T { type X <: A }
higher kind trait T[X[Y]] trait T { type X <: { type Y }}