Proposal: Changes to Implicit Conversions

Proposal: Changes to Implicit Conversions

Hello Scala Community!

This thread is the SIP Committee’s request for comments on a proposal to change how implicit conversions work in the language.

Summary of the proposal

Scala 2 already has implicit conversions, which are implicitly available instances of scala.Function1 (or “[methods] convertible to a value of that type” SLS 7.3). In short, this proposal adds a class scala.Conversion to the standard library, as a subtype of Function1. Implicit conversions are changed to be given instances of the new type. No new syntax is required. Old-style conversions will be phased out. A language flag will be required in order to either define or use “undisciplined” conversions, that is, conversions between types defined elsewhere.

This is one of a set of proposals that are collected under “Contextual Abstractions” in the Dotty docs.

This proposal and the others are motivated, together, in this section:

And then further material specific to conversions only is here:

Related discussions

Note: To channel discussions, we are dealing with the individual “Contextual Abstractions” proposals in separate threads. But it would be good to read and absorb all proposals there together before discussing individual parts.

The most relevant other discussions in this area are:

Note that both of these topics have now grown very long (632 posts and counting). But also, “I believe we are on the home stretch”, says Martin, so it’s appropriate to now also consider side topics such as conversions.

Because the various “Contextual Abstractions” proposal aren’t entirely independent of each other, the topic of conversions has already been touched upon repeatedly in the above threads. The following sections summarize and link to those sub-discussions.

Should conversions always be explicit?

This suggestion has been made at least twice, by @jdegoes and @jdolson:

  • Could/should we “Remove Implicit Conversions” entirely?

Then also a very similar suggestion made by @jdolson here:

Martin has responded as follows:

  • Updated Proposal: Revisiting Implicits
  • He writes: “I agree that a typeclass for explicit conversions via as would be useful”, but it’s unclear if he believes this typeclass (he suggests calling it Convertible) would exist in addition to, or instead of, Conversion.

@morgen-peschke notes that he has already made use of such a typeclass; see Updated Proposal: Revisiting Implicits

Followup posts on this subject continue through December 10th.

How are conversions related to extensions?

This was raised by @Ichoran at Updated Proposal: Revisiting Implicits

“I don’t feel that extensions are sufficiently unified with conversions, which can do exactly the same thing according to the docs. Either conversions shouldn’t allow you to call methods (i.e. a method call would not be a request to convert the type), or the unification should be clearer. In particular, all the extensions should be instances of Conversion…”

Martin was dismissive (“I think we are pretty much settled on the current design” of extensions). But there was some followup discussion about extension method syntax, which seemed to be shut down by Seb’s conclusion: “We should indeed forbid to call extension methods as if they were normal methods” (Updated Proposal: Revisiting Implicits) A few further posts followed, ending on January 8.

This thread of discussion was somewhat revived by @morgen-peschke in early February, in messages such as Updated Proposal: Revisiting Implicits – it wasn’t clear to me if this was directly applicable to the other questions about the design of conversions, or whether the Convertible typeclass was just being used as an example of bringing out broader issues around the overall implicits design.

Other conversions questions so far

Alternative implicits proposal

There is also an alternative implicits proposal submitted by Eyal Roth; the overall discussion thread on that is here:

And the section of the proposal specific to conversions is here:

For discussion

  • Is the proposal clear?
  • Should this proposal be accepted by the committee for Scala 3?
  • Should the proposal be modified before acceptance?
  • Naming. Are we to call these conversions “given conversions” now? (Currently the doc still says “implicit conversions”.)

Time frame

This topic will remain open for at least one month, to allow sufficient time to gather feedback.

5 Likes

DSLs use implicit conversions and I don’t think it’s likely to change.

3 Likes

Thanks for the comprehensive review of where things stand!

I continue to think that conversions and extension methods should either be simply syntactic sugar for each other, or should enable non-overlapping things. That is, either

  • Conversions automatically call code based on type inference or type ascription but not method names.
  • Extension methods do care about method names

or

  • Conversions automatically call code based on type inference, type ascription, and method names
  • Extension methods are syntactic sugar for creation of an anonymous conversion to a class with the methods being extended.

Everything else seems to muddle the two features together.

(For the record, I think I mildly prefer the first option–they are separate features that do separate things. But I could go either way.)

3 Likes

When dealing with DSLs, the one defining the conversion probably also defined the use site of where its “intended” for the conversion to be used. Those cases can also be encoded with a typeclass.

Despite thar, I’m still in favor of keeping conversions as powerful as they are in Scala 2. The new warnings are great, but a bit worried about performance, and the loss of path dependant conversions.

1 Like

Quill’s DSL uses implicit conversions to convert T => Quoted[T] (as well as Quoted[T] => T) so that things like this is possible:

val q = quoted { query[Person] }
run(q.map(p => p.name))

If you fully expand this, there are quote and unquote auto conversions:

val q = quoted { query[Person] }
run( quote( unquote(q).map(p => p.name) ) )

If not for auto conversions, these things would need to be done explicitly (at least the unquote part).

6 Likes

Don’t forget that these are macro conversions, that are getting gutted specifically - Updated Proposal: Revisiting Implicits. For example, you’ll have to define a parent trait that will throw a runtime error, and users will always be able to summon both Conversion[T, Quoted[T]] & Conversion[Quoted[T], T] as runtime values - and both of these will blow up at runtime

There was a brief preliminary discussion of this at the SIP retreat today. Summary:

  • All committee members who expressed opinions were either strongly or weakly in favor of the proposal in general.
  • There doesn’t seem to be any committee interest in the all-conversions-must-be-explicit option.
  • Nor any interest in bringing back implicit def.
  • Martin and several others were sympathetic to the concerns of @Ichoran and others about overlap with extension methods, but no one on the committee sees a good way to address them, either.
  • @nicolasstucki thinks we should consider somehow supporting path-dependent conversions. I invite him to post here about it.

Other questions and points didn’t come up. We’ll re-discuss after this thread’s one-month period has passed.

1 Like

I was also thinking about this. One way to support them would be to change the type that marks implicit conversions from Conversion[A, B] to just Conversion, which would be a marker type that can be combined with any function type of arity 1. With this change, the example given in the proposal would look like the following:

given (Conversion & (String => Token)) {
  def apply(str: String): Token = new KeyWord(str)
}

An example with a dependent function type would look like so:

given (Conversion & ((e: Entry) => e.Key)) {
  def apply(e: Entry): e.Key = ...
}

An alternative solution could be to have a dedicated arrow syntax for Conversion. For instance, ~>. Then, in the same way that we treat the type:

(e: Entry) => e.Key

as a syntax for the type

Function1[Entry, Entry#Key] {
  def apply(e: Entry): e.Key
}

We could treat the type:

(e: Entry) ~> e.Key

as an alias to:

Conversion[Entry, Entry#Key] {
  def apply(e: Entry): e.Key
}
1 Like

The review is really helpful! Thank you, that had to take a long time to assemble.

For the time being, I strongly object this proposal, and I would like to attempt and restate my objections in a concise manner:

  1. I have the feeling that this proposal will create a point of no return for givens, which are in my opinion extremely experimental and controversial, and I have many reservations to them (expressed in other discussions).

  2. Even if we accept givens, this proposal introduces a new Conversion trait / class with its own unique semantics. Introduction of “special traits” seem to me as a step towards irregularity of the language, and the more special the semantics of such traits are, the more irregular the language becomes. For further discussion on this – which started as a response to this comment – see this thread.

  3. IIUC, the unique semantics of Conversion are extremely powerful and significantly differ from the rest of given instances; that is, it is (a) applied without bounds, and (b) applied without an explicit method invocation. I believe this will only introduce further confusion into the semantics of implicits / givens.

For these reasons, I believe conversions deserve their own distinct construct.


I like this idea.

I would also like to have an option for extensions to be expressed as a single stateless instance, which is somewhat equivalent to having an object with the same extension methods, but with the specific instance given as a special parameter (like self in Python). If I’m not mistaken, opaques are planned to be implemented with such “singleton extensions”?

I’m not sure this could be expressed with conversions though.

@SethTisue - I suggested two ways in which overlap with extension methods could be addressed. Were the drawbacks of either discussed? If yes, what was/were the sticking point(s)?

2 posts were split to a new topic: DelayedInit and StringContext

Sorry, I’m unable to improve on what I already wrote above. Is there anything else in the minutes (https://docs.scala-lang.org/sips/minutes/2020-03-11-minutes.html)? And/or, perhaps someone else from the committee will chime in.

As best I can recall, that section of the discussion didn’t last very long and didn’t get very specific.

2 posts were merged into an existing topic: DelayedInit and StringContext

Note that neither this change, nor the PR by Nicolas Stucki (https://github.com/lampepfl/dotty/pull/8523) would support implicit conversions with dependencies in the implicit parameter list:

implicit def x(a: A)(implicit tc: TC[a.T]): tc.Out

The signature of Conversion cannot support such conversions at all. There are quite a few such conversions in akka-http and other libraries I listed in the issue in dotty

Shouldn’t ((a: A) => (tc: TC[a.T]) ?=> tc.Out) & Conversion work? At least if dependent function types, context queries and conversions all work together correctly.

2 Likes