Updated Proposal: Revisiting Implicits

The forum needs an emoji reaction for “I’m not sure what you mean by that.”

The confused emoji, plus raised skeptical eyebrows.

Your example doesn’t compile surprisingly for me.

I did raise a dotty issue along the lines of, Do you really mean for implicit resolution to pick the opposite of what Scala 2 chooses? And the answer was yes.

So I think the team is very tolerant of tickets in that vein, if the ticket shows compilation results.

Those tickets serve as documentation for other confused people.

I haven’t followed all the discussions, but if this is in response to SIP proposals it’s not for “We think this is easy” reasons, but for things like keeping the language complexity down, and maintaining compiler speed, etc.

I don’t see @smarter talking down to you, but I can understand the frustration on having to uplift and relocate a discussion into another forum (a dotty issue), and I encourage you to.

With regards to macros and its API, I suspect it at this stage sending a PR to the dotty repo with your WIPs you might get you the guidance sorely needed.

1 Like

It would be more regular if as was mandatory, so that you would have to write:

given as T = x
given as T { ... }
1 Like

That would only be more regular if the : in type-inferred vals were mandatory as well, i.e.,

val x: = foo

instead of

val x = foo

From the current docs it seems as also must be used in:

given (using outer: Context) as Context

There is no name as Type there, so what are the rules for when as must be used?

1 Like

I was really hoping after @smarter’s last comment that this would simply die, but if comments are going to be added on the tag end of this thing without going back and reading the whole discussion, then perhaps it’s worth providing a summary.

I made this comment about the proposal referencing typeclass definition boilerplate:

That sparked a series of questions about what, exactly, I meant by that, which I did my best to explain. Concrete examples were requested:

After this point, things get weird. I attempted to provide those concrete examples, like this one containing a permalink to one class which has that behavior:

And this comment, which minimizes it down to something that would fit in scastie, were met with terse redirections to go read the docs. This one is pretty representative:

Note that, on the whole, the concrete examples appear to have been taken as intended, so I’m somewhat dubious that the problem is on my end. I’d link the responses, but they’re temporally adjacent so they’re easy to find, and I’d like to avoid dragging anyone else into this mess.

Apparently, @smarter thought I was kvetching about either the proposal or trying to report a bug, rather than trying to explain a misunderstanding about the current state of things that isn’t addressed by the proposal.

I can see how, from their point of view, it was intended as a helpful redirection back on topic. From my point of view the terse nature and off-topic suggestions came off as condescending and dismissive. I’m not sure how this disconnect between, “Concrete examples would be nice”, and “go look at the docs” happened, and I frankly don’t care at this point.

The bit about the macros has been covered to death in other threads. A good summary of why I find this unhelpful and a form of “talking down” can be found here.

For the record:

  • I like the current proposal
  • The definition boilerplate, is a thing, but it’s is less than in Scala 2
  • While it’d be nice to reduce it further, it just isn’t worth it to me to continue to engage

I believe efforts are being made to have better code assistance for implicits, in which case it seem to me that having an additional character or two is less of a problem.

The road to improving productivity is not just about faster code writing, but also about better code readability; we need to strive for a balance between the two.

Then I believe the real problem here is the lack of deliberate mechanisms for conflict resolution and prioritization. Having to rely on nested traits seem to me like a workaround that should be replaced with a dedicated syntax – one that does not increase the “import tax”.

Did you notice the very subtle (and undocumented) semantic difference between implicit val and object? Where one (object) is propagated to the 3rd scope which doesn’t directly import the implicit, while the other (val) does not?

There is an irregularity here. I’m not even sure if this difference is indeed between object and val or maybe there’s something else that is affecting the availability of the implicits in the 3rd scope.

How about implicit? To me it seems that the new proposal is almost identical to the old implicits. Moving from Scala 2 syntax to Dotty feels more like an anagram than a conceptual change, which only begs the question - how implicits are now simpler and easier to understand?

1 Like

IMHO: I think it is harmful suggestion.
If someone really needs to read imports to understand his programme there is something wrong in his application design.

Usually I don’t read and don’t write imports and I happy with that.

2 Likes

I couldn’t agree more with that. imports do not convey logic, they are merely a reference or an index. That is at least the general notion in most programming languages I know of.

Alas, in Scala, imports may convey logic because they provide implicit mechanics as well, and this is why I believe implicits should be included in a scope (which is different than a mere reference) via another mechanism / syntax / keyword.

See the part on lenses in my alternative proposal which addresses this issue.

I literally just meant your code doesn’t compile.

Maybe you meant foo(42)?

So I don’t see what distinction you’re making. Implicit scope is pretty well documented. All the parts of the required type contribute to implicit scope.

Oh lol, yeah I fixed that, it was a copy-paste mistake.

Is the distinction still not clear after the fix?

The syntax in the docs explains that aspect: as must be used if any of the three optional components that precede it is present. These are

  • a name
  • a type parameter list
  • using clauses

Yes, I know you can given t as T = x. That part’s fine.

You are correct that I am explicitly complaining about the mental shift needed to go between named given definitions and anonymous given aliases.

The reason is that these two are the most common ones I want to use. The given t as T = x form is nice to have for future-proofing, but is kind of pointless; we already have a name for t: x! And if you’re porting old code, you already have the name for given t as T { ... } because it used to be implicit val t: T = new T { ... }.

Like I said, I don’t have a great solution. It just is jumpy in a way that I don’t think will entirely go away with time, at least not with everyone. Coupled with the awkward grammatical form, I think it’s liable to make the feature less appreciated than it should be.

Just wanna say that I love this new using keyword is introduced! :slight_smile:
We use given for providing an implicit, and using for taking an implicit parameter.
Those are two different things, and it’s much, much more readable this way! Thanks!

1 Like

Thanks for the positive feedback!

2 Likes

I believe we are on the home stretch. given/using is the clear winner as far as I can see.

There’s one detail that we should still discuss. We currently use given/as for instances everywhere. I.e.:

given intOrd as Ordering[Int] ...
given t as Table
given ctx as Context = ForkJoinContext()

One reason to do so was that the form

given t: Table

looks like an abstract definition, which it is not. In fact,

given t as Table

is analogous to

implicit object t extends Table

A body is not required in either case. This analogy of as with extends is a good reason for keeping it for regular instance definitions.

But for aliases we do have a choice. Maybe it should be

given ctx: Context = ForkJoinContext()

instead? This would introduce an irregularity wrt regular instance definitions, but would align given aliases with using clauses and also with given in patterns. Indeed I can already write

val (given ctx: Context, n) = (ForkJoinContext(), 42)

Here, the given prefix marks the pattern-bound variable ctx as a given.

So far, the compiler supports both : and as for given aliases, but we will have to drop one of them. So, which one should it be?

[EDIT] Another reason for : in aliases is the special case of expressing a given whitebox inline function. I.e., the equivalent of

implicit inline def f(using A) <: B = ${...}

With : this would be

inline given f(using A) <: B = ${...}

But with as, we have instead

inline given f(using A) as _ <: B = ${...}

The latter needs another piece of specialized syntax.

3 Likes

I like it

Another argument for using : instead of as in given aliases, is that in SQL as is used for aliases but in a different meaning. So there could be some “false friends” confusion for someone coming from SQL learning Scala seeing “given alias” and as.

On the other hand:

The syntax given [T]: Foo[T] = foo looks different than any other usages of : in Scala (as far as I know). Would it be possible to write simply given [T] Foo[T] = foo instead?