Sorry I confused *
with _
. But this should do the trick with -Ykind-projector
in Scala 3. I believe at some point this is intended to change to _
iirc.
Thanks, it does indeed work with *
and the flag, and yes you’re right that in 3.2 it will work with _
and no flag (IIUC). ? : Showable
has my vote then.
I’ve read this proposal a couple times over. I am sympathetic with the motivation for having some unnamed context bound for an unnamed type parameter. I never liked that we introduced unnamed given
and using
instances, but since we did, it is not a stretch to also want them in this context.
Now this is one of those proposals where the desired semantics are pretty much clear and unarguable. But not completely. Using the hypothetical :
-based syntax, it’s pretty clear that
def showAll(xs: List[? : Showable]): Unit
must desugar to
def showAll[T : Showable](xs: List[T]): Unit
which itself already has an existing desugaring as
def showAll[T](xs: List[T])(using Showable[T])): Unit
However, it’s not so clear when the original definition defines a type parameter of its own. For example
def showAll[A](xs: List[? : Showable], y: A): Unit
(don’t be hung up on what the purpose of that method would be; it’s irrelevant)
Now suddenly we need to add a type parameter, but there is already one (or several). There is no precedent in Scala for inserting a type parameter this way.
The closest that comes to it is generic extension
clauses with generic methods. For example
extension [A] (xs: List[A])
def myFoldLeft[B](z: B)(f: (B, A) => B): B = ...
which desugars into a method with multiple type parameter lists:
def myFoldLeft[A](xs: List[A])[B](z: B)(f: (B, A) => B): B = ...
The difference is that here, there is always a term parameter list in the middle.
Based on that precedent, I believe that we should add a synthetic parameter list. To preserve natural call sites, I think the additional type param list should come after the first one. So
def showAll[A](xs: List[? : Showable], y: A): Unit
would desugar as
def showAll[A][T : Showable](xs: List[T], y: A): Unit
Now, about the syntax.
First, it’s been mentioned a few times to "just do like Rust and use impl Showable
". I am very opposed to that for two reasons:
- While hard-core FP developers don’t like to hear it, in Scala, implementing something is an inheritance concept: implementing a trait with
extends
, implementing an abstractdef
in a subclass/subtrait, etc. So if we seeList[impl Showable]
, it reads as a list of something that implementsShowable
, hence thatextends Showable
. - As was mentioned before, Rust uses
impl Showable
here becauseimpl Showable
is also the thing used when defining a typeclass instance forShowable
(see here). In Scala, we usegiven
at that spot; but we also chose to usegiven
only for definition sites, andusing
for the corresponding use sites. So, if we want to follow Rust’s logic and be consistent with existing Scala syntax, the only correct choice here isusing Showable
.
Second, let me talk about the :
-based syntax I used above:
def showAll(xs: List[? : Showable]): Unit
It was already mentioned that, while it looks fine when inside a type parameter list, it is very weird at the top-level:
def show(x: ? : Showable): Unit
is, unfortunately, quite bad. Moreover, it has the problem that ?
cannot be meaningfully replaced by anything else. For example,
def showAll(xs: List[T : Showable]): Unit
doesn’t make sense. We are in a position where T
must already be defined (it can’t be introduced as a new binding here). You might say we could write
def showAll[T](xs: List[T : Showable]): Unit
but that would be more clearly written as
def showAll[T : Showable](xs: List[T]): Unit
to begin with.
So I don’t think the ? : Showable
is really viable. It would introduce inconsistencies.
Omitting the ?
altogether, as List[: Showable]
, makes it even worse at the top-level, x: :Showable
, no matter how we want to explain away that it makes sense.
In conclusion, I’m not quite set on the definitive syntax here, but my “least-bad” choice would be using Showable
:
def showAll(xs: List[using Showable]): Unit
def show(x: using Showable): Unit
seems fine enough, consistent with existing Scala syntax, and even consistent with Rust’s idea (for those who like to take that as a good argument).
Now it might be a bit weird to explain that the desugaring chain
def showAll(xs: List[using Showable]): Unit // 'using'
// becomes
def showAll[T : Showable](xs: List[T]): Unit // ':'
// which then becomes
def showAll[T](xs: List[T])(using Showable[T]): Unit // back to 'using'
goes from using
to :
and back to using
.
We may want to introduce using
as an alternative to :
when defining context bounds, to solve this path:
def showAll[T using Showable](xs: List[T]): Unit
may make sense. And, well, it would remove one magic use of the non-searchable :
…
OK, that’s all I had for today.
Makes sense to me. In particular, I think it’s a good sign that, midway through, I wound up going "Well, then you probably want a using
alias for :
" – and then found that you’d come to exactly the same conclusion. So this seems to be intuitively consistent…
I agree that show(x: ? : Showable)
is a little ugly, and that’s why I originally didn’t propose something like it. But it’s really only a little ugly, and it is totally consistent with the rest of the language. In fact, it’s a little funny that I can’t write show(x: ?)
but can write show(x: List[?])
– why force me to write Any
?
But more importantly, I don’t think the worry about def showAll(xs: List[T : Showable]): Unit
holds up – you can make the same objection about <:
:
def showAll(xs: List[T <: Showable]): Unit // not permitted
def showAll(xs: List[? <: Showable]): Unit // permitted
def showAll[T <: Showable](xs: List[T]): Unit // permitted.
Implementing the feature was actually pretty easy (you can see a first cut here). I think that’s a pretty strong indication that it already gels well with the language: I was able to combine the fact that type parsing already is okay with context bounds in some environments and is already okay with type bounds on ?
in others.
Regarding using
: I would be overall in favor of the change to the language if context bound syntax were changed, but I’m not sure that would be backwards compatible, since ? using Showable
is infix sugar for using[?, Showable]
. I think you’d need to make using
a hard keyword, though I’m not sure.
I agree with the worries about introducing a type parameter. Once multiple type parameter lists are available, I hope this will be less of a concern. I’m actually not sure: when the compiler inserts a using
clause for context bound sugar, does it append to the last using
param list (if any) or make a new one? We should just follow whatever happens there.
Thanks for the detailed reply!
I just want to point out a couple things:
Proposed syntaxes with ?
involved save no characters over just being able to introduce new type parameters while inside the argument list. E.g. def foo(x: T)
instead of def foo[T](x: T)
. So, def foo(x: T : Show)
would be better than bringing a ?
.
But, allowing introduction of type parameters in the argument list, risks accidentally introducing a type parameter via a typo, when you wanted to refer to an existing one (for example, def foo(x: Thing, y: Tihng): Thing
would no longer be an error, just incorrect)
Maybe it could work for the 90% case, if there was a constraint that only one type parameter can be introduced in that fashion and otherwise it’s an error. But still, I don’t think listing the type parameters up front is enough of a burden to make this worth it (even in the most egregious of shapeless Aux
cases)
Perhaps stating the obvious, but you could again make the same argument for <:
( def showAll(xs: List[? <: Showable]): Unit
) – and for that matter, any wildcard type parameter outside of case matches.
I think there are reasons to argue that context bound syntax should be fundamentally different from the existing type bound syntax (in particular, the fact that context bound syntax is sugar but <:
is a compiler primitive), but I don’t know that characters saved is a good one.
And of course there’s no need to debate whether foo(x : T : Show)
is better, since foo(x : T <: Show)
has already been rejected.
@odersky @sjrd is there a path forward for this proposal? Several of the comments are negative, but after realizing how close the language already is to supporting this sugar, I find the arguments against this proposal (most of which focus on larger changes to the language) to be relatively unconvincing. Given that @odersky seems to be in favor, I’m unclear what to do next (since I believe the real SIP process is dormant). I have a PR available, but I’m not sure if I should just put it up now for approval, or try to follow some other feedback process.
(To be clear, I mean the updated proposal, which only proposes allowing ? : TL
as sugar for a context bound on an anonymous type).
You may open a PR if you want. Otherwise, please be a bit more patient for a few more weeks or so, until we can fully put the SIP process back on track. We have been making active progress there in the past few weeks (and my going over all the recent pre-SIP is part of that effort). Sorry for the delay.
Great to hear. I opened a PR, but I don’t want to unduly rush the process – happy to wait if that’s preferred!
I personally feel that
- the current syntactic sugar
i.e. unnameddef showAll[S](using Showable[S])(xs: List[S]): Unit = xs.foreach(x => println(x.show))
using
clause is good enough (it scales well if you need to eventually name the parameter or if you want to use multiparameter type class, …), and - we don’t need any more syntactic sugar in this area and
- even the context bounds (
[S: Showable]
) should be eventually dropped from the language to make it more regular.
As I said above, most folks in this thread who object to the proposal also largely object to context bounds in general, which is of course a reasonable position. After having seen how close the language already is to supporting the proposal, I feel like it’s hard to argue against its inclusion without taking the position that context bounds should be removed entirely, or changed to use impl
or using
or something similar.
The language is currently here
• |
def foo[T• U] |
x: ? • U | x: List[? • U]` |
---|---|---|---|
<: or >:
|
|||
: |
and the proposal brings it to
• |
def foo[T• U] |
x: ? • U | x: List[? • U]` |
---|---|---|---|
<: or >:
|
|||
: |
(and honestly I think we should make the final go away in the name of regularity too, even if it’s a little meaningless).
I think the question of whether to change the :
in the second row – or eliminate that row altogether – is orthogonal. Personally I see a good argument for changing the context bound syntax but not a good argument for removing it entirely.
Also note that
def showAll(xs: List[? <: Showable]): Unit
is already (effectively) sugar for
def showAll[S <: Showable](xs: List[S]): Unit
though of course the sugar is thicker for the context bound than the type bound.
I don’t mind context bounds, though if we were designing the language from scratch we would probably not add them in as they no longer pull their weight in terms of added complexity for saved syntax.
On the other hand, I find your proposal ugly, confusing, and unnecessary. It buys you almost nothing beyond saving a couple of characters. I especially don’t like the idea of implicitly introducing hidden type parameter lists.
Nevertheless impl
traits seem to be very popular in Rust, even though one could also write them with an explicit type parameter there.
Lurker.
Proposed(?) inline type parameter syntax
def m(generic :T) = ???
looks extremely alarming to me. Typos is one reason, but a lack of an import is even worse.
If I type a name of a type in IDE and it does not complain, I will assume I already imported it in this file.
Swift has also added this same feature, previously you would need a type parameter to a function (to capture the concrete type at the call site for a generic argument) and now you can avoid that by writing x: some Foo
in the parameter list
I like the proposal of introducing the requirement of the type class at its “use site” because it seems to communicate better what the intention of the function is.
For example, I would read List[?: Showable]
(or List[using Showable]`) as “a list of something that has a Showable”. It makes the code more direct for the human than the logic language of “for all T that has a Showable, a list of T” (or “for some T”, depending whether your are reading the interface or the implementation).
I have however faced more than once the desire to implement the problem described in this stack overflow question: scala - List of classes implementing a certain typeclass - Stack Overflow. Namely: I want a list of values, possibly of distinct types, all of which implement a certain type class. The goal, of course, is to use the respective type class instance with each value.
While it can be resolved with implicit conversions in Scala 2, it is brittle, hard to understand and slow to compile. I wonder if that syntax should not be reserved for the compiler to deal exactly with that problem, rather than to the proposed sugar.