Allow specification of a subset of type parameters

Ah I see. But summon or implicitly is actually a simple library function, whereas the typelevel analogue would have to be built in. And it’s an “implicit” only in a rather wide sense of the word.

But in any case, once we have named type arguments, it’s an open question whether we want to do this as well.

If this is something that will eventually be explored, I think “infer” is a good name for a soft keyword in type position.

2 Likes

In fact, there’s a strong case that the marker for a missing type argument should be “_”. Note that after the current migration a “_” in a type application will mean lambda abstraction, aligned with its meaning for term application. So Map[Int, _] is [X] =>> Map[Int, X]. Therefore, new Map[Int, _] is a polymorphic function over the “value” parameter of Map, and that type argument is inferred. This is analogous to new Map being a polymorphic function with two inferred parameters, which are both inferred.

By analogy, f[A, _] should also mean polymorphic lambda abstraction in function applications. So f[A, _] means a polymorphic function in one argument, which is inferred. In fact, we could also allow to give it explicitly to be consistent. So

  f[A, _][B]  =  f[A, B]

just like

  f(a, _)(b)  =  f(a, b)    // with the meaning of "reduces-to"

for terms.

I think things are falling into place now. If we have type lambda abstraction by means of “_”, then named type arguments compose with that cleanly. Named arguments are re-ordered according to
the definition order and missing bindings are filled in with “_”. E.g.

Map[V = String]  =  Map[_, String]

Subsequent type arguments can be inferred or given explicitly. Examples:

new Map[V = String](1 -> "a")   // infers `K = Int`

or

new Map[V = String][Int]

A separate question is whether we want to allow curried type parameter definitions at definition site. I.e.

def f[A][B]
class Map[K][V]

instead of

def f[A, B]
class Map[K, V]

This will be very hard to do for classes, but it’s technically no problem for methods. However, as I said before, it would likely cause a split in the ecosystem how type parameterization is defined. So I think we should do this only once all the other discussed elements are settled, if at all.

12 Likes

I was thinking exactly the same thing today.
The the akka’s ActorRef.unsafeUpcast[U:>T] will be ActorRef.unsafeUpcast[U:>T = Any]

In my mind ? made more sense, as I understand it as meaning “A type parameter that I don’t want to specify”

Another problem with the polymorphic function idea is that type parameters for polymorphic functions do not get inferred:
(which is extra weird given context function params are)

val poly = [T] => (x: T) => x

val mono: Int => Int = poly // error:
// Found:    (Playground.poly : [T] => (T) => T)
// Required: Int => Int

Also in the case Type[A, B][C], it is not clear what Type[A = Int]/Type[A, _] would mean: [T1] => [T2] =>

Type[A, T1][T2] or Type[A, T2][T1] ?

1 Like

I think this is clear? For curried term parameters:

def foo(a: Int, b: Int)(y: String): Double = a

val inferred: Int => String => Double = foo(1, _)

So I think the answer is Type[A, T1][T2]?

1 Like

If “what would a function call do” is our north star, this seems pretty straightforward.

Type[A, B][C] would be analogous to f(a,b)(c) so Type[A, _] would be analogous to f(a, _), which should be equivalent to b => f(a,b) and b => c => f(a,b)(c).

That means Type[A, _] should be equivalent to B => Type[A, B] and B => C => Type[A,B][C]

Or, in table form:

Double-Curried Single Curried Fully Expanded
Type[A, _] B => Type[A,B] B => C => Type[A,B][C]
f(a,_) b => f(a,b) b => c => f(a,b)(c)

This argument also applies to the split between currying via underscore and currying via multiple parameter groups, and I don’t think anyone can honestly argue that this is an issue in practice.