Here’s an update. After the discussions on this list and more in-person discussions with various people I got the feedback that it will be very hard to “sell” an alternative keyword for implicit instance definitions. I seem to be in the strict minority here to prefer something else over implicit.
Since there are more important aspects than the choice of keyword, I believe it is best to give in on this point. The proposal is still to have a unique form to define implicit instances, but the syntax will be implicit for, instead of repr of or implied for. There was some pushback against using for in this new role, but since Rust does exactly the same thing and people do not seem to have a problem with it there, I believe that will be OK for Scala as well.
I think just using implicit is the most pragmatic choice!
As a point of reference, Rust is adopting postfix .await as a new keyword for async and they’re thinking about changing their match (which currently has syntax match foo { Thing => result, Other => whatever }) to be dotted infix notation (foo.match{ Thing => result, Other => whatever }).
So while they don’t have experience with this yet, it’s interesting to note the trend given our discussion about whether to allow f given x to be expressed as f.given(x).
Yep. If various people predominantly preferred implicit over alternative keywords then perhaps they would also give opinion on given.
IMO big mistake in discussing notation for given is the unrealistic example. What I would give voters to compare is e.g.
Future(expression).given(ec1).map(x => f(x)).given(ec2).map(x => g(x)).given(ec3)
// which could be formatted to
Future(expression).given(ec1)
.map(x => f(x)).given(ec2)
.map(x => g(x)).given(ec3)
vs
((Future(expression) given ec1).map(x => f(x)) given ec2).map(x => g(x)) given ec3
// which could be formatted to
((Future(expression) given ec1)
.map(x => f(x)) given ec2
).map(x => g(x)) given ec3
I believe that the example you show represents a misuse of implicit parameters. Overriding the implicit parameter with something explicitly given should be rare and not be done casually, as in this example. Consider the downstream cost: You now have lots of operations floating around that all take different implicit values! It looks to me that’s an active encouragement to get incoherence problems. So coming up with a syntax that stands out less for this case is not necessarily a win.
I am not saying we have to take pains to discourage it, but argue that, for given arguments, the conceptual regularity of language syntax trumps convenience.
This is bad reason for just blindlessly copying syntax from other languags. for in Rust makes sense because its not overloaded with anything else in the language, In Scala for is also used in for comprehensions.
Is it that hard to use another word that is not already used in the language? I mean we can also use with (which is being deprecated in Dotty anyways).
Okay I stand corrected but I am not a fan of overloading keywords, it seems like Rust did it by accident when trying to copy other languages which have for comprehensions. In Scala its even more confusing since we do use for in similar expressions but again its just syntax sugar in the form of for expressions with generators.
Of course you could structure that differently or redesign Future’s combinators, but straight conversion to infix notation would lead to rather ugly or weird code. Therefore we need either voting for dotted notation or some guidance on Future usage to avoid ugly code in Dotty.
As for regularity: if all methods can be invoked in both dotted notation and infix notation then there’s already an irregularity (?):
def (x: Double) ** (y: Double) given (z: Double) = Math.pow(x, y) * z
val pow = x ** y // infix notation
val pow = x.**(y) // dotted notation
val sum = x + y // infix notation
val sum = x.+(y) // dotted notation
If we take type keyword then sometimes it accepts a dot as in value.type and sometimes not as in type X = Int. Does that hurt language regularity?
I’d prefer given inside of a parameter list. This gives the right visual impression of precedence and associativity in more complex cases:
f(a)(given b, c)(d, e)
In f(a) given (b, c)(d, e) the precedence looks wrong. And f(a).given(b, c)(d, e) makes it look like (d, e) is a parameter list for given and not associated with f.
I think that while in an absolute sense, the names don’t matter too much, because people will learn whatever names these constructs are given, it is still a mistake to go with majority consensus in groups that suffer from high survivorship bias (i.e. contributors to this forum).
Talking to hundreds of current and former Scala developers around the world, and working with many companies (some who are still using Scala, some who have moved onto other languages, and others contemplating such a move), my strong impression is that implicits are regarded as one of the more confusing, esoteric, and easily abused aspects of the language by mainstream users.
Now, there exist a high-skilled, highly experienced in-group of Scala power users—such as those who are most likely to contribute to this forum—who love the power provided by implicits. Further, many of these users would even prefer that that language features reside at the level of mechanics rather than intention; but in my experience, the views of these highly experienced power users are not shared by the majority of existing and potential Scala users.
In short, implicits have a tarnished name due to implicit abuse (not inside this forum, of course!), and 3.0 is a nice opportunity to break with that, and give this fresh take an untarnished name (ideally inspired from successful constructs in other languages); and avoid the further overloading of the implicit keyword that will temporarily exist in 3.0 as the variety of predecessor forms continue to be supported (I can see that headline now: Scala 3 adds even more implicit forms—not strictly true but a lot of people will see that and take it at face value).
So it’s anecdotal evidence vs another anecdotal evidence. Perhaps we should do a proper survey if we want objective data. Question stated is also important.
But it appears to me that this proposal is mostly about a refocused syntax for implicit parameters, and by that I mean that everything that could be done before can still be done in the new syntax, and is still prone to all the abuse and confusion generated by implicit parameters.
I’m afraid that upon finding out that this “fresh take” only really is a rebranding of fundamentally the same underlying implicit mechanisms (in particular, that it does not correspond to the simpler and more manageable version found in Rust and Haskell), people are not going to regard the “fresh take” any more kindly than if it had retained the “implicit” name in all honesty.
At least from what I’ve seen, there are 2 aims of this proposal:
Remove the capability for implicit conversions
Tweak the syntax to encourage the best-practice usages of implicits
While I think that the proposal veers a bit further toward “punish certain usage of implicits” than I’m strictly comfortable with, overall I agree with the general direction. While there is an element of rebranding, I think it’s generally a means to an end (breaking old behavior patterns), more than an end goal.