Limiting implicit conversions to right-hand-side (definition arguments) for Dotty

After watching the dotty ScalaDays lectures, I was thinking about implicit conversions and their “evilness”, also considering Multiversal Equality and this dotty PR.

For an infix expression, I think the good use-case for implicit conversions, is that I have a concrete left-hand-side expression and if the right-hand-side type does not fit, an implicit conversion can try to make it fit. However changing the left-hand-side to make the conversion fit, is what I think creates the major problem with implicit conversions.

Example 1:
Let’s assume I have an Int to String widening implicit conversion in scope. Currently,

1 + "Some" //returns 1Some
"Some" + 1 //returns Some1

If we limit implicit conversion to the right-hand-side (only the definition arguments), then:

1 + "Some" //fails
"Some" + 1 //returns Some1

This is much more logical, IMO.

This proposal refers to implicit conversions using definitions and not implicit classes that are used to enhance a concrete left-hand-side expression.

I’ve had a hard time figuring out what problem this fixes, and we’ve discussed this at https://gitter.im/lampepfl/dotty?at=5939666a142826e972968625.

I suspect the scenario where this is most problematic is the one in this thread:

In general, given def ===[A](v1 : A, v2: A): Boolean = ..., inverse conversions from A to B and from B to A, and variables a : A and b : B, what does a === b mean?

Overall, I suspect an actual linter/compiler warning/error for such inverse conversions might be a better goal. But I wouldn’t know right away how to specify when to give this warning, if the conversions are more generic, so it’s not obvious how to achieve this goal.

I can demonstrate a viable two-way conversion use-case.
I’m a contributor to the singleton-ops library. The libray enables us to express literal type operations, and additionally we have equivalence in value, meaning:

val a : 3 = implicitly[2+1] //+ is an Infix type alias of a singleton operation
val b : 2 + 1 = 3
val c : 3 + 0 = implicitly[2+1]

To achieve the above I have the following implicit conversions in place:

  • Int with Singleton to Op (Op is a singleton-operation composition)
  • Op to Int with Singleton
  • Op to Op

The conversions take place if the actual value is equal (there is an implicit check).
I currently don’t have a === in place for Op, but I would imagine a === b would fail due to ambiguity.

Disabling two-way conversion will cripple this library drastically.

1 Like

Very well — I shouldn’t be surprised there’s a good use case. I’m not an expert in that library but I remember it’s a serious effort. I’m sure many rely on LHS conversions and I’m not sure all uses can be converted to implicit classes; that’s not a real argument without examples, but it suggests to me caution.

I’ll leave the stage for other comments.

That would break extension methods.

Incidentally, an implicit Int=>String won’t let you write 1 + "Some" if Int has a + method with no String overload. So Int is not a good example (there is no implicit conversion involved at all). And (1: Any) + "Some" works without your conversion because there’s already a conversion called any2stringadd, which is actually providing + as an extension.

Extension methods work just like any implicit conversion – the expected type does not match the actual type without it – except that the “expected type” is a structural type (in this case “something having a + method etc.”). At least that is what the error messages imply.

If you were to hide any2stringadd and instead have an implicit Any=>String, it would work with the same principle – the compiler is not somehow looking for a String type, but rather is looking for "something that has a def +(String) method. If you eliminate that you eliminate all extension methods.

Unless you want to write a new proposal for how extension methods should work…

That would break extension methods.

But it says

This proposal refers to implicit conversions using definitions and not implicit classes that are used to enhance a concrete left-hand-side expression.

I know implicit classes are sort-of-sugar over implicit methods… except they already aren’t just sugar. Again, I’m not sure about the original issue…

Also, even with the restriction to implicit classes on the LHS, you can create ambiguous cycles, it’s just harder to do by accident: you need to use your implicit class as a type in other code, and create implicit conversions from the implicit class. But (I think) that’s unusual, and since implicit classes declare clearly their role it should be easier to create an inspection flagging such scenarios.