Clause Interweaving, allowing `def f[T](x: T)[U](y: U)`

In the context of my master semester project, I’m implementing Clause Interweaving, I would like to have your feedback!

What it is:

It allows methods to have more than one type parameter clause, and for them to be arbitrarily interwoven with other clauses, basically a generalization of type currying:

def foo[T,U](x: T)(y: U): Z
def bar[T](x: T)[U](y: U): Z

Note that at the use site, this behave very similarly to something already valid, but inelegant like:

def bar[T](x: T): { def apply[U](y: U): Z }

The only difference being overloading resolution, where the new system allows access to U and y where the old one doesn’t.

The full technical details can be found it this PR to the scala 2 doc (as there is no scala 3 doc yet):


Dependent methods:

The new syntax allows a clean way to have type parameters depend on values like this:

trait Key { type Value }

trait DB {
  def getOrElse(k: Key)[V >: k.Value](default: V): V

Omitting obvious types:

It allows us to separate types parameters the compiler can infer from the ones it can’t, for example:

trait Function[Arity <: Int & Singleton, Return]
def constant[A <: Int & Singleton][R](x: R): Function[A,R] = ???

In constant, A is impossible to infer if there is no expected type, so we have to specify it, however, the R can always be inferred, since we curried them, we can specify only A like so:

val oneInputToZero = constant[1](0) // A = 1, inferred: R = Int 
// Type: Function[1, Int]


Classes and types must still have at most one type parameter clause, and it has to be the first one, this might be confusing to users.

The behaviour at constructor-call-site can still be emulated for classes through a constructor proxy:

class MyClass[T,U](x: T, y: U){...}
object MyClass{
  def apply[T](x: T)[U](y: U) = new MyClass(x, y)


I hope you find this feature useful, and am happy to answer any questions !

I believe this feature represents a somewhat dramatic change, and as such I expect people will have strong opinions about it, if you have any thoughts or feelings about this change, please share them here.
This will help getting a feel for how the community at large would react !


This looks really useful. Can you say a little bit about the difference between

def bar[T](x: T)[U](y: U): Z


def bar[T](x: T): [U] => U => Z


The latter allocates a function value whereas the former erases to def bar(x: Any, y: Any): Z and does not.


There’s another benefit:

Overloading Resolution


def bar[T](x: T): [U >: Int] => (y: Int) => U
def bar[T](x: T): [U >: String] => (y: String) => U

bar[Char]('c')(0) // error


def bar[T](x: T)[U >: Int](y: Int): U
def bar[T](x: T)[U >: String](y: String): U

bar[Char]('c')(0) // finds correct method by looking at the type of y

From an end-user POV, it feels delightfully “Scala-ish” – it has that characteristic of just “doing the right thing”, and while I don’t expect I would use it often, that Dependent-methods use case is common enough to be compelling. I like it at first blush, and hope that it works out.


I’ve wanted the ability to do something like the dependent method for ages, and separately wanted a way to omit obvious types. This is killing 2 birds with one stone. Great.

Thank you for your excitement ^^

In some cases, you might not even need those changes, as:
Dependent result types are already possible (for both methods and functions)

What this would add is the ability to have dependent parameter types for methods

For functions it’s also already possible, but a bit awkward:

val getOrElse: (k: Key) => [V >: k.Value] => (default: V) => V = ???

I have cleaned and opened the PR, it now links and explains everything, and is more up to date that this,
It also contains the implementation details, so I invite you to give it a look and give me some feedback on there !


Oops, forgot to link the PR, it’s here


Since I’m in a streak of reviewing proposed language changes: I fully support these changes. The proposal looks really strong to me. I don’t see any issue with the design nor with backward compatibility.


Perhaps we could allow identifiers to be part of the interleaving as well?

def a[T](x: T) b[U](y: U) c d[V](z: V): T | U | V

Call site:

a(1) b(2) c d(3)

On the whole I feel like it would create a lot of problems with parsing at call-site

Do you have an example of something that would work better with extra identifiers ?

That is an entire different beast, which would require a separate proposal.

1 Like