Pre-SIP Discussion: Named type arguments

I often come across this problem:

def foo[A, B, C, D, E](x: A)(implicit ev1: Bar[C], ev2: Baz[D]): E =
  {}

Now, sometimes the compiler needs help figuring out some of the type arguments of foo and not all. But there is no way to specify just some of them, you need to end up specifying all of them which is incredibly verbose at call-site.

Example: https://stackoverflow.com/questions/46612142/generically-morph-a-class-into-another-using-shapeless-labelledgenerics

A work around some people use is to use nested def to anonymous classes:

def foo1[A, B, E](x: A) = new {
  def foo2[C, E](implicit ev1: Bar[C], ev2: Baz[D]): E = {}
}

This is a big hack. I propose a way to name and specify type arguments e.g.:

foo[A = String, E = Int)(x)

and the compiler figures out B and C and D. We already have named arguments, so it makes aesthetic sense to also have named type arguments.

11 Likes

I think this is a very good idea.

If/when we do this, I think some type arguments in the standard library should be renamed as well to be more meaningful, because they may end up being referenced by name. For example, Either[+A, +B] should become Either[+L, +R].

This is indeed great feature, and to support it: it’s already available in dotty - http://dotty.epfl.ch/docs/reference/named-typeargs.html

Some sort of type parameter currying would also be nice. So you could do

def foo[B, E][A, C, D](x: A)(implicit ev1: Bar[C], ev2: Baz[D]): E = ???

foo[Int, String](4.2)

Something similar would also be possible if you added polymorphic function values to what is already available in Dotty, like proposed here.

2 Likes

I also think this is a good idea, but I think we should (in the context of a SIP) play with the idea of going beyond what Dotty (currently) supports. From the Dotty feature page:

Type arguments must be all named or un-named, mixtures of named and positional type arguments are not supported.

I think it would be preferable to emulate the named/default parameters feature instead: positional type arguments “fill up” the argument list from the left; named arguments must all come after the last positional argument. Type parameters that weren’t specified by either name or position are inferred as normal.

Thus:

trait Applicative[F[_]] extends Apply[F] { 
  def point[A](a: A): F[A]
}

def point[F[_], A](a: A)(implicit F: Applicative[F]): F[A] =
  F.point(a)

point[List](3) // : List[Int]

would also be possible. This isn’t quite the same as the curried type parameter lists that @Jasper-M proposes, and I think it would be somewhat more flexible.

Big :+1: from me.

2 Likes

As I understand it in Dotty you don’t have to keep repeating parameters when you inherit. So using longer meaningful names for type parameters as we do for values becomes a lot better trade-off.

In Dotty you can curry the type parameters of type synonyms by using a type function:

scala> type T[X] = [Y] => (X,Y)
// defined alias type T = [X] => [Y] => (X, Y)
scala> def f[A:T[Int]] = ()

It’s useful, for example, to leverage the concise implicit parameter syntax:

scala> def f[A:T[Int]] = ()
def f[A](implicit evidence$1: ([X] => [Y] => (X, Y))[Int][A]): Unit

// same as:

scala> def f[A:[Y] => T[Int][Y]] = ()
def f[A](implicit evidence$1: ([X] => [Y] => (X, Y))[Int][A]): Unit

So I think it would completely make sense to allow a more convenient curried type parameter syntax type T[X][Y] = (X,Y), and to generalize it to method type parameters.

Personally speaking, I’d say this proposal is not actually controversial. I think it seems just a matter of resources for designing and implementing the details, but I’m not sure how much effort this would be.
Similar proposals were also discussed when talking about multiple implicit argument lists and so on.

And for reference, when Paul Philipps extended Scalac’s parser to parse T[X][Y] (so that the current work around mentioned in the OP works at all), the change went in without any opposition (there should be a thread in scala-internals for any interested archeologists).

Note that named type arguments expose the name of the type parameters to the users of a given API endpoint. If a library maintainer changes the name of the type parameters of method foo, that is a source breaking change.

Such a change will also require tooling support: IDEs will need to rename type arguments not only in the definition site but also the use sites.

5 Likes