Path-dependent types

Marcin Rzeźnicki

mrzeznicki@iterato.rs

... we use this stuff

Path-dependent types

  • Od Scali 2.10
  • Dla language lawyers (za Scala Reference Book):

    Paths are not types themselves, but they can be a part of named types and in that
    function form a central role in Scala’s type system.

  • The base types of a type selection S #T are determined as follows (...) T must be a

    (...) type, which is defined in some class B . Then the
    base types of S #T are the base types of T in B
    seen from the prefix type S.

     

     

W ogólności ...

W Scali typy zagnieżdżone są związane z konkretną instancją typu zewnętrznego (outer), a nie z samym typem zewnętrznego (jak w Javie). Składnia p1.p2.T wskazuje na typ T poprzez ścieżkę prowadzącą przez konkretne typy zewnętrzne - stąd nazwa path-dependent

Najprostsza ścieżka

object MyService {
  class MyServiceLogger {
    def log(message: String) = println("1: "+message)
  }
  val logger = new MyServiceLogger
}

logger ma typ: MyService.MyServiceLogger

Trochę komplikacji

trait Service {
  trait Logger {
    def log(message: String): Unit
  }
  val logger: Logger
}

object MyService extends Service {
  class MyServiceLogger extends Logger {
    def log(message: String) = println("1: "+message)
  }
  override val logger = new MyServiceLogger
}

logger ma dalej typ: MyService.MyServiceLogger <: MyService.Logger <: Service#Logger

typ: ?.Logger (? to nasza ścieżka)

definicja typu MyService.MyServiceLogger <: MyService.Logger

Trochę komplikacji

trait Service {
  trait Logger {
    def log(message: String): Unit
  }
  val logger: Logger
}

object MyService1 extends Service {
  class MyService1Logger extends Logger {
    def log(message: String) = println("1: "+message)
  }
  override val logger = new MyService1Logger
}

object MyService2 extends Service {
  override val logger = MyService1.logger
}

logger ma typ MyService1.MyServiceLogger

???

Gdzie jest błąd?

class A {
  class B
  var b: Option[B] = None
}
val a1 = new A
val a2 = new A
val b1 = new a1.B
val b2 = new a2.B
a1.b = Some(b1)
a2.b = Some(b1)

Jak i po co używać?

Przykład ukradziony z Neophyte's Guide to Scala

object Universe {
   case class Character(name: String)
 }

class Universe(name: String) {
  import Universe.Character
  def createLoveStory(
    lovestruck: Character,
    objectOfDesire: Character): (Character, Character) = (lovestruck, objectOfDesire)
}

val starTrek = new Universe("Star Trek")
val starWars = new Universe("Star Wars")

val quark = Universe.Character("Quark")
val jadzia = Universe.Character("Jadzia Dax")

val luke = Universe.Character("Luke Skywalker")
val yoda = Universe.Character("Yoda")



Jak i po co używać?

Problem !!!


starTrek.createLoveStory(lovestruck = jadzia, objectOfDesire = luke)




Jak i po co używać?

class Universe(name: String) {
  case class Character(name: String)
  def createLoveStory(
    lovestruck: Character,
    objectOfDesire: Character): (Character, Character) = (lovestruck, objectOfDesire)
}

val starTrek = new Universe("Star Trek")
val starWars = new Universe("Star Wars")

val quark = starTrek.Character("Quark")
val jadzia = starTrek.Character("Jadzia Dax")

val luke = starWars.Character("Luke Skywalker")
val yoda = starWars.Character("Yoda")

PDT to the rescue

Jak i po co używać?

starTrek.createLoveStory(lovestruck = jadzia, objectOfDesire = luke)

found   : starWars.Character
required: starTrek.Character
               starTrek.createFanFictionWith(lovestruck = jadzia, objectOfDesire = luke)

Nie ma problemu :-)

def createLoveStory(u: Universe)(lovestruck: u.Character, objectOfDesire: u.Character) =
  (lovestruck, objectOfDesire)

Alternatywnie - dependent method types

PDT + parametry typów

Problem: chcemy zakodować type-safe operacje na liczbach całkowitych mod N + chcemy żeby były tak transparentne jak to możliwe. Nie można mieszać liczb z różnych ciał modN tj. nie ma sensu operacja (i mod N) + (i mod M)

Rozwiązanie: Możemy  reprezentować pola Galoise'a (liczby mod N) jako typy. Wtedy, z definicji PDT, każdy typ "ze środka" takiego pola będzie unikalny (bedzie miał inną ścieżkę)

PDT + parametry typów

case class Z(modulus: Int) {
  sealed class Modulus {
    val value = modulus
  }
}

class IntMod[N <: Z#Modulus] private(val value: Long) extends AnyVal 

val z1 = Z(10000103)
val z2 = Z(20000208)

val i1 = 10: IntMod[z1.Modulus]
val i2 = 100000000: IntMod[z1.Modulus]
val i3 = 10: IntMod[z2.Modulus]
val i4: IntMod[z2.Modulus] = i1




error: type mismatch;
 found   : IntMod[z1.Modulus]
 required: IntMod[z2.Modulus]

PDT + implicity

case class Z(modulus: Int) {
  sealed class Modulus {
    val value = modulus
  }

  object Modulus {
    implicit val mod = new Modulus()
  }
}

class IntMod[N <: Z#Modulus] private(val value: Long) extends AnyVal 

object IntMod {
  implicit def intAsIntModN[N <: Z#Modulus](i: Int)(implicit modulus: N): IntMod[N] =
    new IntMod[N](i % modulus.value)
  implicit def longAsIntModN[N <: Z#Modulus](i: Long)(implicit modulus: N): IntMod[N] =
    new IntMod[N](i % modulus.value)

  implicit def intModN2Long(a: IntMod[_]): Long = a.value

  implicit def intModN2Int(a: IntMod[_]): Int = a.value.toInt
}





PDT - mały catch

Article is really cool, but I'm not sure if given example is clear enough. I have a problem with the situation when I create two instances of Z which are semantically equal

val z1 = Z(23)
val z2 = Z(23)

I would expect that
implicitly[z1.Modulus =:= z2.Modulus]
is `true` but is not.

Case-study - Effect.io

Pamiętać: zmiana ekranu !!! :-)

 

 

Path-dependent types

By Marcin Rzeźnicki

Path-dependent types

  • 1,030