Proposal to Add Implied Instances to the Language

I don’t think it works very well for anonymous definitions of extension methods:

repr {
  def (xs: List[T]) second[T] = xs.tail.head
}

Perhaps it might be better to not let extension methods be part of this syntax, and have a seperate keyword for them?

extension {
  def (xs: List[T]) second[T] = xs.tail.head
}

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.

So here’s the proposal updated to reflect the new syntax.

I’ll make a new thread with the updated proposal shortly.

7 Likes

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).

1 Like

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
5 Likes

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.

6 Likes

match now has several roles that go beyond its usage as a method. I.e. inline matches, match types. So, I think we will stick with infix.

2 Likes

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).

https://doc.rust-lang.org/reference/expressions/loop-expr.html#iterator-loops

2 Likes

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.

1 Like

Future is a very frequently used type and it’s not rare to provide explicit thread pool, e.g.:

Future(dao.getName()).given(dbQueriesEc)
  .map(name => httpClient.getCurrentData(parameter)).given(netRequestsPool)
  .map(params => computeLocally(params)).given(computationsPool)

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.

2 Likes

In f(a) given (b, c)(d, e) the precedence looks wrong.

It is wrong. You have to write (f(a) given (b, c))(d, e) in that case.

2 Likes

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).

7 Likes

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.

2 Likes

Yes second that.

Indeed which is why I think sometimes its better to actually make up a new word, so as its meaning can be taught rather mis-inferred.

I think these are really good points and I share your concerns.

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.

4 Likes

I agree there are more important thing:

  • error reporting
  • code assistant
  • import automation

Maybe, It will be possible to do configuration files if the compiler needs additional information for that.

Impilicts is a calling card of scala, so may be it worth it.

1 Like

Yeah, like renaming “null” to “uninitialized”, because people hate null.

3 Likes

At least from what I’ve seen, there are 2 aims of this proposal:

  1. Remove the capability for implicit conversions
  2. 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.