Proposal to Add Implied Instances to the Language

Yes, of course, but that’s extra work and is much less readable. Is there some reason implicit val doesn’t get a first-class expression but lazy val does? Is it really just that the designers think implicit val is much less often used?

It doesn’t seem onerous to support multiple new syntax forms, implied def t for T = ... and implied val t for T = ... (and lazy val), unless there’s a reason to think that implied val and lazy val are so rarely necessary that it’s worth not supporting them directly just to omit the word “def” in implied def. If that’s the claim, it sounds very surprising to me; perhaps it’s true, but I would like to hear it argued.

(If we allow implied val, then I think we must use implied def and not just implied t for T to avoid confusion.)

The whole point of implicits redesign is to make them a bit more high level and also to give them separate semantics.

Also see here: https://dotty.epfl.ch/docs/reference/contextual-implicit/relationship-implicits.html
It actually says that implicit IntOrd will be mapped to implicit object IntOrd and other types of implicits can be mapped to defs, vals or cached. I think it’s important to take that into consideration when designing new syntax.

I dislike canon's connotations. I assume it’s standing in for “canonical”, and the problem is, that’s not necessarily true.

For example, consider Ordering. Is the forward or reverse ordering “canonical”? Or consider the integer Monoid – is addition “canonical”? What about multiplication?

If we were in a world with typeclass coherence, canon might well be appropriate. But with Scala’s view of things, where there are potentially several valid instances in different contexts, it doesn’t feel quite right…

2 Likes

In fact, the redesigned proposal makes the implementation use a val for non-parameterized instance definitions. The val is lazily computed, except if the right hand side is pure and simple to compute. This was done so that there’s no fundamental difference in computation between the normal and alias forms.

See point 3 in

https://dotty.epfl.ch/docs/reference/contextual-repr/relationship-implicits.html

Do we really want the .given() syntax instead of the infix? I think that the infix notation looks cleaner if given is going to be a reserved word (it reads so fluently in English):

implicit Monoid[Option[A]] given Semiring[A]

implicit Monad[Map[_, R]] given Semiring[R]

implicit Length[H :: T, Succ[P]] given Length[T, P]

Apart from that, this is exactly my proposal earlier :slight_smile:

The linked doc says:

Alias implicits map to implicit methods. If the implicit has neither type parameters nor a given clause, the result of creating an instance is cached in a variable.

That contradicts the post that started this discussion, which says implied always behaves as a def:

Each time an implied instance of ExecutionContext is demanded, the result of evaluating the right-hand side expression is returned.

I’m no longer sure what the intended behavior is. If it’s sometimes a val and sometimes a def, I think that would be very confusing for users. If it’s always a def, I still think not making the val and lazy val usecases equally convenient is a pity, and would like to hear why that is.

Now that I’ve read it, it makes me think of edge cases where the values returned by repeatedly calling the parameterless right-hand-side expression are “not interchangable” in some relevant way, and it’s not obvious to the user whether they’re being cached or not, and there is not convenient syntax for making sure they are (or aren’t) cached.

Yes, a parameter-less method that returns different things on subsequent calls (especially for a type being used implicitly) is very bad practice, but this creates the opportunity to have subtle bugs hard to spot even with a debugger.

Regardless, I would like to hear why you think that it’s not important (there’s no convenient syntax) for users to explicitly force a val or lazy val when this behavior would default to a def.

(I need to go into a meeting and won’t reply again on this thread for the next hour or two, sorry about that.)

I agree that the uncertainty on how implicit aliases are mapped is rather bad. Maybe add lazy modifier to implicit, e.g.

lazy implicit IntOrd: Ord[Int] = ???

Looks weird and is somewhat inconsistent with the rest of language :confused:

Implicits’ redesign is also about finding use cases that are worth supporting and also those that aren’t. If you have concrete examples, show them.

Now that I’ve read it, it makes me think of edge cases where the values returned by repeatedly calling the parameterless right-hand-side expression are “not interchangable” in some relevant way, and it’s not obvious to the user whether they’re being cached or not, and there is not convenient syntax for making sure they are (or aren’t) cached.

I believe that’s excluded by the wording “pure and simple to compute” (which for me implies total).

Another word for type instance? Types are sets of things, and a thing in a set is called an element.

As long the effect of new is observable, i.e. eq exists, only value types can be proven ‘pure’. Suppose the returned value doesn’t override equals, and then I use it as a dictionary key. Suddenly it matters a lot whether each call returns a new instance!

How about der? It could stand for derive, derivation or derivative (depending on whether you prefer verbs, nouns or adjectives) and can play the role of a twin to def (much like val has its var although for def/der the evilness is not so clear cut). An additional bonus is that if you squint while you look at it and keep the origins of Scala in mind, it becomes the German version of the earlier proposed the.

1 Like

Here are two examples from the standard library. In object ExecutionContext.Implicits:

implicit lazy val global: ExecutionContextExecutor = impl.ExecutionContextImpl.fromExecutor(null: Executor)

In Predef:

implicit val StringCanBuildFromCanBuildFrom[String, Char, String] = new CanBuildFrom …

I can think of more examples, but they’re still anecdata; I’m not sure how to gather convincing quantitative data.

A common usecase in my own code is when some recursive-implicit typeclass is expensive to instantiate (e.g. a serializer), slowing down compilation (and increasing code size) because it’s implicitly generated for the same type in many callsites, and so I put an implicit val in that type’s companion object.

Is it possible to define an abstract implicit in this new syntax? For example:

  implicit val some : Thing 

What is the the new syntax for this? Do implied instances support an override modifier?

@odersky, I misunderstood earlier; I didn’t realize that the ‘redesigned’ proposal you linked to contradicts (supercedes) the one posted at the start of this discussion, by always creating some kind of val for parameter-less declarations.

If I understand correctly now, there are three possible outcomes in the new proposal. Quoting, my words in [brackets]:

  1. If the representative has neither type parameters nor a given clause, the result of creating an instance is cached in a variable. [Behaves like a lazy val]
  2. If in addition the right hand side is pure and cheap to compute, a simple val can be used instead.
  3. [Otherwise, a def is created.]

I still think the difference between 1 and 2 is opaque and confusing. “Cheap to compute” isn’t even defined. The lazy-ness of a val can matter a lot, and not knowing if a val is lazy or not seems bad. It might matter when the call is made, e.g. I may want it to be lazy because it would access other expensive lazy vals. And a val in an object can promote exceptions to classloading failures (ExceptionInInitializerError).

I also fear that the result could change in either direction unexpectedly, as small implementation changes affect the compiler’s idea of what is “cheap to compute”. And how does it affect separate compilation? Will the build tools know to trigger recompilation of implied callsites when the implementation they call starts or stops being “cheap to compute”?

The bigger difference, between a def and any kind of val, is clear in the new proposal: you get some kind of val whenever possible, i.e. iff there are no arguments. I do still think it’s common to want to create a val or lazy val when the algorithm create(i.e. cache an instance) and requiring two separate declarations makes this needlessly complex.

I assume there are good reasons that implied def and implied val were discarded (and also implied object for instance definitions), but I did want to point out that this usecase is made much less convenient.

1 Like

Is it possible to define an abstract implicit in this new syntax? For example:

  implicit val some : Thing 

It’s explained in https://dotty.epfl.ch/docs/reference/contextual-repr/relationship-implicits.html,
towards the end. No it’s not possible to define an abstract implicit. What you do instead is define
a plain abstract value and an implicit alias. Something like this:

   val some: Thing
   implied for Thing = some

The advantage is that you inject in the implicit space once, rather than having to coordinate abstract
implicit with concrete overrides. For the same reason, override is not supported on implied instances.

The observable effect is the one of a lazy val. The rest are implementation shortcuts the compiler is
allowed to make that can improve performance but do not change the observable effect. It wouldn’t even be need to be specified. Compilers are always allowed to change representations as long as
the observable effect stays the same.

Right, because it only happens if the implementation is pure. I’m sorry for taking up your time with this.

I think repr is short and snappy and representative reads well in sentences.

My only other suggestion would be archetype as a noun, which is a word with perhaps a stronger emphasis on how an object is the “one correct instance that may be in scope”, but this doesn’t compress well to a four-letter keyword!

archetype ListOrd[T] of Ord[List[T]] given (ord: Ord[T]) {
  def compare(xs: List[T], ys: List[T]): Int = ???
}

My only other suggestion would be archetype as a noun

Ooh i like this one also.

I’m not a big fan of abbreviations, especially highly nonstandard ones like repr or others that involve just dropping arbitrary letters mid-word. It’s ok to require a couple extra keypresses (or an editor/ide macro or automated expansion) to improve clarity. Storage space is cheap; mental computation tax to expand nonobvious abbreviations is not.

2 Likes