Any reason why scala does not explicitly support dependent types?

Any reason why scala does not explicitly support dependent types?

There are path dependent types and I think it is possible to express almost all the features of such languages as Epigram or Agda in Scala, but I'm wondering why Scala does not support this more explicitly like it does very nicely in other areas (say, DSLs) ? Anything I'm missing like "it is not necessary" ?


Syntactic convenience aside, the combination of singleton types, path-dependent types and implicit values means that Scala has surprisingly good support for dependent typing, as I've tried to demonstrate in shapeless.

Scala's intrinsic support for dependent types is via path-dependent types. These allow a type to depend on a selector path through an object- (ie. value-) graph like so,

scala> class Foo { class Bar }
defined class Foo

scala> val foo1 = new Foo
foo1: Foo = Foo@24bc0658

scala> val foo2 = new Foo
foo2: Foo = Foo@6f7f757

scala> implicitly[foo1.Bar =:= foo1.Bar] // OK: equal types
res0: =:=[foo1.Bar,foo1.Bar] = <function1>

scala> implicitly[foo1.Bar =:= foo2.Bar] // Not OK: unequal types
<console>:11: error: Cannot prove that foo1.Bar =:= foo2.Bar.
              implicitly[foo1.Bar =:= foo2.Bar]

In my view, the above should be enough to answer the question "Is Scala a dependently typed language?" in the positive: it's clear that here we have types which are distinguished by the values which are their prefixes.

However, it's often objected that Scala isn't a "fully" dependently type language because it doesn't have dependent sum and product types as found in Agda or Coq or Idris as intrinsics. I think this reflects a fixation on form over fundamentals to some extent, nevertheless, I'll try and show that Scala is a lot closer to these other languages than is typically acknowledged.

Despite the terminology, dependent sum types (also known as Sigma types) are simply a pair of values where the type of the second value is dependent on the first value. This is directly representable in Scala,

scala> trait Sigma {
     |   val foo: Foo
     |   val bar: foo.Bar
     | }
defined trait Sigma

scala> val sigma = new Sigma {
     |   val foo = foo1
     |   val bar = new foo.Bar
     | }
sigma: java.lang.Object with Sigma{val bar: this.foo.Bar} = $anon$1@e3fabd8

and in fact, this is a crucial part of the encoding of dependent method types which is needed to escape from the 'Bakery of Doom' in Scala prior to 2.10 (or earlier via the experimental -Ydependent-method types Scala compiler option).

Dependent product types (aka Pi types) are essentially functions from values to types. They are key to the representation of statically sized vectors and the other poster children for dependently typed programming languages. We can encode Pi types in Scala using a combination of path dependent types, singleton types and implicit parameters. First we define a trait which is going to represent a function from a value of type T to a type U,

scala> trait Pi[T] { type U }
defined trait Pi

We can than define a polymorphic method which uses this type,

scala> def depList[T](t: T)(implicit pi: Pi[T]): List[pi.U] = Nil
depList: [T](t: T)(implicit pi: Pi[T])List[pi.U]

(note the use of the path-dependent type pi.U in the result type List[pi.U]). Given a value of type T, this function will return a(n empty) list of values of the type corresponding to that particular T value.

Now let's define some suitable values and implicit witnesses for the functional relationships we want to hold,

scala> object Foo
defined module Foo

scala> object Bar
defined module Bar

scala> implicit val fooInt = new Pi[Foo.type] { type U = Int }
fooInt: java.lang.Object with Pi[Foo.type]{type U = Int} = $anon$1@60681a11

scala> implicit val barString = new Pi[Bar.type] { type U = String }
barString: java.lang.Object with Pi[Bar.type]{type U = String} = $anon$1@187602ae

And now here is our Pi-type-using function in action,

scala> depList(Foo)
res2: List[fooInt.U] = List()

scala> depList(Bar)
res3: List[barString.U] = List()

scala> implicitly[res2.type <:< List[Int]]
res4: <:<[res2.type,List[Int]] = <function1>

scala> implicitly[res2.type <:< List[String]]
<console>:19: error: Cannot prove that res2.type <:< List[String].
              implicitly[res2.type <:< List[String]]
                    ^

scala> implicitly[res3.type <:< List[String]]
res6: <:<[res3.type,List[String]] = <function1>

scala> implicitly[res3.type <:< List[Int]]
<console>:19: error: Cannot prove that res3.type <:< List[Int].
              implicitly[res3.type <:< List[Int]]

(note that here we use Scala's <:< subtype-witnessing operator rather than =:= because res2.type and res3.type are singleton types and hence more precise than the types we are verifying on the RHS).

In practice, however, in Scala we wouldn't start by encoding Sigma and Pi types and then proceeding from there as we would in Agda or Idris. Instead we would use path-dependent types, singleton types and implicits directly. You can find numerous examples of how this plays out in shapeless: sized types, extensible records, comprehensive HLists, scrap your boilerplate, generic Zippers etc. etc.

The only remaining objection I can see is that in the above encoding of Pi types we require the singleton types of the depended-on values to be expressible. Unfortunately in Scala this is only possible for values of reference types and not for values of non-reference types (esp. eg. Int). This is a shame, but not an intrinsic difficulty: Scala's type checker represents the singleton types of non-reference values internally, and there have been a couple of experiments in making them directly expressible. In practice we can work around the problem with a fairly standard type-level encoding of the natural numbers.

In any case, I don't think this slight domain restriction can be used as an objection to Scala's status as a dependently typed language. If it is, then the same could be said for Dependent ML (which only allows dependencies on natural number values) which would be a bizarre conclusion.

[PDF] A Path To DOT: Formalizing Fully Path-Dependent Types, faithfully model Scala which does allow general path-dependent types. The restriction to types dependent on variables rather than paths is not merely A second challenge of extending DOT with support for general paths is to reason is that the constructor for x will strictly evaluate all of x's val fields when x is created. You are not supposed to need implicits to make path-dependent types work. In fact, there is at the moment no way in Scala to prove to the type system something like a.T <: b.T based on an implicit. The only way is for Scala to understand that a and b are really the same value. Here is a simple design that does what you required:


I would assume it is because (as I know from experience, having used dependent types in the Coq proof assistant, which fully supports them but still not in a very convenient way) dependent types are a very advanced programming language feature which is really hard to get right - and can cause an exponential blowup in complexity in practice. They're still a topic of computer science research.

Proposal to remove Existential Types from the language, Existential types that can be expressed using only wildcards (but not forSome ) are still supported, but are treated as refined types. inference and using existentials to be able to drop explicit type annotations in two scenarios: insight into any other use cases that would not be covered with wildcards or dependent types. Rafe's answer is true about the rationale for why something like i++ doesn't belong in Scala. However I have one nitpick. It's actually not possible to implement i++ in Scala without changing the language. In Scala, ++ is a valid method, and no method implies assignment. Only = can do that.


I believe that Scala's path-dependent types can only represent Σ-types, but not Π-types. This:

trait Pi[T] { type U }

is not exactly a Π-type. By definition, Π-type, or dependent product, is a function which result type depends on argument value, representing universal quantifier, i.e. ∀x: A, B(x). In the case above, however, it depends only on type T, but not on some value of this type. Pi trait itself is a Σ-type, an existential quantifier, i.e. ∃x: A, B(x). Object's self-reference in this case is acting as quantified variable. When passed in as implicit parameter, however, it reduces to an ordinary type function, since it is resolved type-wise. Encoding for dependent product in Scala may look like the following:

trait Sigma[T] {
  val x: T
  type U //can depend on x
}

// (t: T) => (∃ mapping(x, U), x == t) => (u: U); sadly, refinement won't compile
def pi[T](t: T)(implicit mapping: Sigma[T] { val x = t }): mapping.U 

The missing piece here is an ability to statically constraint field x to expected value t, effectively forming an equation representing the property of all values inhabiting type T. Together with our Σ-types, used to express the existence of object with given property, the logic is formed, in which our equation is a theorem to be proven.

On a side note, in real case theorem may be highly nontrivial, up to the point where it cannot be automatically derived from code or solved without significant amount of effort. One can even formulate Riemann Hypothesis this way, only to find the signature impossible to implement without actually proving it, looping forever or throwing an exception.

[PDF] Decidable Subtyping for Path Dependent Types, types, a key feature of both DOT and Scala, and critical for the encoding of Java generics. supports all the same patterns of generic Java class usage identified by Note: while they are not explicitly defined with bounds, type Address For this reason, Wyvcore introduces a simplified form of intersection types, type. The reason why it’s not an Option is because the json body parser will validate that the request has a Content-Type of application/json, and send back a 415 Unsupported Media Type response if the request doesn’t meet that expectation. Hence we don’t need to check again in our action code.


The question was about using dependently typed feature more directly and, in my opinion, there would be a benefit in having a more direct dependent typing approach than what Scala offers. Current answers try to argue the question on type theoretical level. I want to put a more pragmatic spin on it. This may explain why people are divided on the level of support of dependent types in the Scala language. We may have somewhat different definitions in mind. (not to say one is right and one is wrong).

This is not an attempt to answer the question how easy would it be to turn Scala into something like Idris (I imagine very hard) or to write a library offering more direct support for Idris like capabilities (like singletons tries to be in Haskell).

Instead, I want to emphasize the pragmatic difference between Scala and a language like Idris. What are code bits for value and type level expressions? Idris uses the same code, Scala uses very different code.

Scala (similarly to Haskell) may be able to encode lots of type level calculations. This is shown by libraries like shapeless. These libraries do it using some really impressive and clever tricks. However, their type level code is (currently) quite different from value level expressions (I find that gap to be somewhat closer in Haskell). Idris allows to use value level expression on the type level AS IS.

The obvious benefit is code reuse (you do not need to code type level expressions separately from value level if you need them in both places). It should be way easier to write value level code. It should be easier to not have to deal with hacks like singletons (not to mention performance cost). You do not need to learn two things you learn one thing. On a pragmatic level, we end up needing fewer concepts. Type synonyms, type families, functions, ... how about just functions? In my opinion, this unifying benefits go much deeper and are more than syntactic convenience.

Consider verified code. See: https://github.com/idris-lang/Idris-dev/blob/v1.3.0/libs/contrib/Interfaces/Verified.idr Type checker verifies proofs of monadic/functor/applicative laws and the proofs are about actual implementations of monad/functor/applicative and not some encoded type level equivalent that may be the same or not the same. The big question is what are we proving?

The same can me done using clever encoding tricks (see the following for Haskell version, I have not seen one for Scala) https://blog.jle.im/entry/verified-instances-in-haskell.html https://github.com/rpeszek/IdrisTddNotes/wiki/Play_FunctorLaws except the types are so complicated that it is hard to see the laws, the value level expressions are converted (automatically but still) to type level things and you need to trust that conversion as well. There is room for error in all of this which kinda defies the purpose of compiler acting as a proof assistant.

(EDITED 2018.8.10) Talking about proof assistance, here is another big difference between Idris and Scala. There is nothing in Scala (or Haskell) that can prevent from writing diverging proofs:

case class Void(underlying: Nothing) extends AnyVal //should be uninhabited
def impossible() : Void = impossible()

while Idris has total keyword preventing code like this from compiling.

A Scala library that tries to unify value and type level code (like Haskell singletons) would be an interesting test for Scala's support of dependent types. Can such library be done much better in Scala because of path-dependent types?

I am too new to Scala to answer that question myself.

Type classes as objects and implicits, CONCEPT pattern and implicits (a type-directed implicit pa- Scala. 1. Introduction. Type classes were introduced in Haskell (Peyton Jones 2003) dynamic dispatching over the instances of T is not available, but in return support for multi-type concepts is better and tion between dependent method types and implicits. The signature of func is that it does not take any parameters and returns a result of the generic type T. As you can see, functions in Scala do not necessarily need parameters even though they can


[PDF] Dependent Type Systems as Macros, For example, Scala supports path-dependent Thus, some code may not run as presented, but runnable examples for all code are in our artifact. not write #%​app explicitly; instead, the macro expander automatically inserts it in front of applied Reason. 9, 1 (2016), 101ś148. Edwin Brady and Kevin Hammond. 2006. justinmchase commented Jul 22, 2014. @RyanCavanaugh You could potentially build in an Ometa-like library that allowed you to do pattern matching of all sorts. You may want a more TS friendly syntax but it would be very compelling. You could do matching on any object not just Types.


Java vs. Scala: Why Should I Learn Scala?, Frankly, we are programmers, and if we're not smart enough to learn a language Scala is a type-safe JVM language that incorporates both object oriented and within a number of those very same Java paradigms that were the reason for developer needing to specify explicitly those things that the compiler can infer. Which of the following does NOT explain why sometimes teams do not select the best solution to a problem? Finding the best solution takes too long. Decision making is often too rational. The problem-solving process is disrupted by emotional issues. There are trade-offs among alternative good solutions.


[PDF] Dependent Types for Class-based Mutable Objects, to support class-based programming and inheritance. A key reason why dependent types are interesting is that they are a smooth extension of simple Finally, when a method does not explicitly declare a return type, the A similar model is provided by Scala's path-dependent types [1] that unify nominal and structural  Even greater autonomy is preserved via Information and Decision Support, because they do not explicitly prescribe or proscribe any alternative: “an item received as general information need have no personal implication for what a person…may do” (Pilnick, 1999, p. 613).