question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Dependent Class Types for Scala

See original GitHub issue

Dependent Class Types for Scala

Motivation

Scala already supports many variants of dependent types (aka. types that depend on terms), e.g.

  • path-dependent types
  • dependent method types
  • dependent function types
  • singleton types
  • literal types

The combination of higher-kinded types and singleton types is another commonly used approach to encode dependent types in Scala:

trait Context {
  type Tree
}

class Helper[T <: Context](val ctx: T) {
  def f(x: Int): ctx.Tree = ???
}

val ctx  : Context   =  ???
val tree : ctx.Tree  =  new Helper[ctx.type](ctx).f(5)

However, the encoding above is tedious in syntax. Instead, most programmers would expect the following code to work:

trait Context {
  type Tree
}

class Helper(val ctx: Context) {
  def f(x: Int): ctx.Tree = ???
}

val ctx  : Context   =  ???
val tree : ctx.Tree  =  new Helper(ctx).f(5)

Compile the code above in Scala 2.12.4 will result in the following error message:

 found   : _1.ctx.Tree where val _1: Helper
 required: Test.ctx.Tree
  val tree : ctx.Tree  =  new Helper(ctx).f(5)

The fact that classes in Scala are ignorant of referential equality also limits the expressive power of dependent function types. For example, given the code below, currently it’s impossible to implement the function f:

class Animal(name: String)

trait Foo[T]
class Bar(val a: Animal) extends Foo[a.type]

val f : (a: Animal) => Foo[a.type] = ???

If we try to implement f with (a: Animal) => new Bar(a), the Dotty compiler will generate following error message:

7 |  val f : (a: Animal) => Foo[a.type] = (a: Animal) =>  new Bar(a)
  |                                                           ^^^^^
  |                                                  found:    Bar
  |                                                  required: Foo[Animal(a)]

But the definition of Bar says that any instance of Bar(a) is a subtype of Foo[a.type]!

Related issues: https://github.com/scala/bug/issues/5712, https://github.com/scala/bug/issues/5700, #1262.

Proposal

To address the problems above as well as make dependent types more useful, we propose dependent class types. We introduce an annotation @dependent that can be used to mark a public field of in a class constructor depenent:

class Animal(name: String)

trait Foo[T]
class Bar(@dependent val a: Animal) extends Foo[a.type]

The compiler should produce a dependent refinement type for dependent class instantiation as follows:

val dog  : Dog =  new Animal("dog")
val bar  : Bar { val a: dog.type @dependent }  =  new Bar(dog)

The subtype checking should allow the following:

// Bar { val a: dog.type @dependent }  <:  Foo[dog.type]
val foo  : Foo[dog.type] = bar

Rules

  1. @dependent can only be used in class primary constructor to decorate non-private, non-mutable, non-lazy, non-overloaded fields.

  2. A depenent refinement type M { val x: T @dependent } is valid if x is a non-private, non-mutable, non-lazy, non-overloaded field of M, and T is a subtype of M.member(x).

  3. If a primary constructor parameter x of a class Bar is annotated with @dependent, the return type of the constructor is refined as Bar { val x: x.type @dependent }. The x in x.type refers the constructor parameter, thus the constructor has a dependent method type.

  4. When getting the base type of a valid dependent refinement type M { val x: T @dependent } for a parent class P, first get the base type B from baseType(M, P), then substitute all references to M.member(x) with the type T.

Implementation

We don’t need syntax change to the language, nor introduce new types in the compiler.

  1. We need to introduce an annotation @dependent.
package scala.annotation

final class dependent extends StaticAnnotation
  1. We need add checks for valid usage of @dependent in class definitions and dependent refinement types. The checks are better to be done in RefChecks, as they are semantic.

  2. We need to update the type checking for constructors, to refine the result type with the dependent refinement { val x: x.type @dependent } if any constructor parameter x is annotated with @dependent. The change in consideration looks like the following:

val oldRetType  = ...
val refinedRetType = termParamss.foldLeft(oldRetType) { (acc, termParams) =>
  termParams.foldLeft(acc) { (acc, termParam) =>
    if (acc.hasDependentAnnotation)
      RefinedType(acc, termParam.name, termParam.termRef)
    else acc
  }
}
  1. We need to change base type computation to handle dependent refinements. The change in consideration looks like the following:
case tp @ RefinedType(parent, name, refine) =>
  val res = baseTypeOf(tp.superType)
  if (tp.isDependentRefinement)
    res.subst(tp.nonPrivateMember(name).symbol :: Nil, refine :: Nil)
  else res

Application

Dependent class types can implement ML-like functor modules:

import scala.annotation.dependent

trait Ordering {
  type T
  def compare(t1:T, t2: T): Int
}

class SetFunctor(@dependent val ord: Ordering) {
  type Set = List[ord.T]
  def empty: Set = Nil

  implicit class helper(s: Set) {
    def add(x: ord.T): Set = x :: remove(x)
    def remove(x: ord.T): Set = s.filter(e => ord.compare(x, e) != 0)
    def member(x: ord.T): Boolean = s.exists(e => ord.compare(x, e) == 0)
  }
}

object Test {
  val orderInt = new Ordering {
    type T = Int
    def compare(t1: T, t2: T): Int = t1 - t2
  }

  val IntSet = new SetFunctor(orderInt)
  import IntSet._

  def main(args: Array[String]) = {
    val set = IntSet.empty.add(6).add(8).add(23)
    assert(!set.member(7))
    assert(set.member(8))
  }
}

Change Logs

  1. Changed @dep to @dependent, thanks @propensive .
  2. Added related issues, thanks @julienrf .
  3. Add application about functor modules

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:1
  • Comments:24 (19 by maintainers)

github_iconTop GitHub Comments

3reactions
liufengyuncommented, Jan 29, 2018

The following code (which compiles and runs with #3936 ) shows that dependent class types can implement ML-like functor modules:

import scala.annotation.dependent

trait Ordering {
  type T
  def compare(t1:T, t2: T): Int
}

class SetFunctor(@dependent val ord: Ordering) {
  type Set = List[ord.T]
  def empty: Set = Nil

  implicit class helper(s: Set) {
    def add(x: ord.T): Set = x :: remove(x)
    def remove(x: ord.T): Set = s.filter(e => ord.compare(x, e) != 0)
    def member(x: ord.T): Boolean = s.exists(e => ord.compare(x, e) == 0)
  }
}

object Test {
  val orderInt = new Ordering {
    type T = Int
    def compare(t1: T, t2: T): Int = t1 - t2
  }

  val IntSet = new SetFunctor(orderInt)
  import IntSet._

  def main(args: Array[String]) = {
    val set = IntSet.empty.add(6).add(8).add(23)
    assert(!set.member(7))
    assert(set.member(8))
  }
}
3reactions
propensivecommented, Jan 26, 2018

I think the use case is very important, though I’ve never been an advocate of more annotations for Scala, so I’d prefer a solution which avoids them…

…however, if it is the best way, could you use @dependent instead? Using @dep saves only a few characters on a (likely) rarely-used annotation, but comes at the cost of some clarity for people who encounter it for the first time.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Path-Dependent Types in Scala - Baeldung
A dependent type is a type whose definition depends on a value. Suppose we can write a List that has a constraint on...
Read more >
Dependent Function Types | Scala 3 — Book
A dependent function type describes function types, where the result type may depend on the function's parameter values. The concept of dependent types, ......
Read more >
Dependent Types in Scala - Some Tips, Tricks and Techniques
Dependent types can be used to pass type information around when solving for types. Implicitly generated traits can be defined in terms of...
Read more >
Scala 3: Path-Dependent Types, Type Projections, Dependent ...
This quick tutorial will show you what dependent types are and how they work in Scala 3, along with dependent methods and functions....
Read more >
Scala 3: Dependent Types, Part I - Medium
Dependent types are types that depend on values. For example, the following declarations are legal in Scala 3: Simple examples of dependent ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found