Pre-SIP: using underscores for type lambdas

Dotty (and probably Scala 2.14) will introduce some syntactic support for type lambdas (the current syntax is described in http://dotty.epfl.ch/docs/reference/type-lambdas.html). Since a term lambda x => foo(x) can be expressed more succinctly as foo(_), it would make sense to have a similar shortcut syntax for type lambdas, but using _ conflicts with wildcard types. https://github.com/lampepfl/dotty/issues/5379 is a proposal to achieve this. Since this involve a rather complicated and potentially painful migration, I encourage you to vote on the github issue, and voice your opinion on the exact details (which version of Scala should understand which syntax ? what should kind-projector do ?). Keep in mind that even if the eventual syntax does not become part of the language until Scala 3.x, you’ll probably already be able to use it long before that using a compiler flag.

8 Likes

Update: it looks kind-projector has now added * as an alternative to ?, though there’s still time to change that before their next release: https://github.com/non/kind-projector/pull/91

I like * as the type-level equivalent of _ for values. It is a little frustrating to have to say [A, B] => A => B if we could instead say * => *. So for example, the const type becomes: type Const[A] = * => A, or alternatively, type Const = [A] => * => A, either of which makes it very, very clear that we don’t care about the second type parameter.

2 Likes

Yeah – I’m not an expert at this level, but I find * rather intuitive…

I use * as a multiplication type operation in singleton-ops type *[A, B] = ???, but I don’t think it’s likely that the two constructs will interact. I also have no idea what will happen to singleton-ops in dotty, since it is “too whiteboxy”.

3 Likes

Because of migration pains, maybe it would be easier to move both type and value lambdas to a new shortened syntax.

Here is a proposal (which has likely been suggested before – sorry if that’s the case):
Use $_ instead of _, and allow $1, $2, etc. for referring to the argument position explicitly.

This way, we can have:

val ls = List(1, 2, 3)
// this:
ls.foldLeft(0)(_ + _)
// is now written:
ls.foldLeft(0)($_ + $_)
// equivalent to:
ls.foldLeft(0)($1 + $2)
// semantically equivalent to:
ls.foldLeft(0)($2 + $1) // which could not be expressed easily before

// another thing we can do now we could not do before:
val people = List(Person("A", 1), Person("B", 2))
val m = people.map($1.name -> $1).toMap
// m == Map("A" -> Person("A", 1), "B" -> Person("B", 2))

// and for types:
implicitly[Functor[Either[String, $_]]]

I find this intuitive, because it resembles usage in familiar languages. (Bash and Perl come to mind; though these languages are notorious for their lack of readability, I don’t think these specific constructs are to blame.)
Moreover, since $ is currently not supposed to be used in identifiers (though it is valid syntax), this change should be mostly backward-compatible.

2 Likes

Is there a problem with just keeping ? ? Seems like the least problematic path overalld

This is highly inconsistent and likely to make an already complex subject (typelevel programming) even more obscure.

? is the default currently, sure it’s inconsistent, but I can’t see how just keeping it makes the subject more complex… At the very least a new learner won’t run into examples with outdated syntax.
You could make an argument that square brackets for arguments in types vs. parentheses for values is inconsistent, and with full dependent types that would even make sense - but so what?

1 Like

I would second it, even if only for the fact that it recalls the higher-kinded syntax * -> * which seems quite elegant and appropriate here.

1 Like

I like the idea of having the numerals available to write non-trivial term lambdas a-la groovy/bash/…

About the $ syntax I’m not sure if it might conflict or even only create confusion in some cases with existing features, like string interpolation or class-files naming… (if java-interop is still a relevant thing, not sure anymore).

Personally I’d go for something lambda-like as in haskell, e.g.

ls.foldLeft(0)(\_ + \_)
ls.foldLeft(0)(\2 + \1)

implicitly[Functor[Either[String, \_]]

since the backslash seems less used in general than $

Yet what I find more worrisome is how hard would it be to backport all existing code to the new syntax, even with the help of scalafix, as the _ is ubiquitous in existing codebases and has several different usage patterns

Far more code uses value lambdas than type lambdas, and this won’t change regardless of syntax. Therefore it seems that breaking compatibility for foo(_) would increase migration pains; why do you think it would decrease them?

It’s a mechanical change that could easily be handled by scalafix, but I’m scared of the amount of syntactic change that Scala 3.0 has collected. Is there a point where someone will argue that there are too many changes all at once, even if each separate change is small and acceptable?

2 Likes

I was thinking of keeping both approaches available for some versions while advertising the new one, and possibly migrating the old value lambda syntax later, over a long time period. It means that in the short term we’d have a way to have a unified lambda syntax, even though the old style is still available for backward compatibility.
Admittedly, I don’t see a radical change like this one being ever enacted. It was more of a thought experiments :sweat_smile:

I have already argued that, and I feel very strongly that we’re taking a very dangerous approach. Move slowly and iterate - #33 by curoli

Martin Odersky’s response, IIUC, was basically that the large changes are essential and the ones that could be separated are small anyway. This doesn’t satisfy me but I haven’t taken the time to respond yet. Feel free to chime in on that thread.