10

I like implicit definitions. They make the code look nice, they make the user feel some features are naturally available on a class when it's just an implicit definition. Yet, I was thinking about JS prototypes, where you can basically define a method on a class you did not write. But if in the next version this class defines a method with the same signature and makes assumptions on its behaviour, you're screwed.

Scala's implicits enable to do almost the same thing with one major difference : the implicit definitions are scoped, therefore there is no risk for the author of a class to have code injected by an implicit definition in someone else's code. But what about the user's code ? Is it protected from a change in the class he's adding implicits to ?

Let's consider this code :

class HeyMan {
    def hello = println("Hello")
}

object Main extends App {
    val heyMan = new HeyMan

    implicit class ImplicitHeyMan(heyMan: HeyMan) {
        def hello = println("What's up ?")
    }
    heyMan.hello // prints Hello
}

Pretty bad, isn't it ? To me, the correct behaviour should be that the implicit definition always shades the real definition, so that the user code is protected from the apparition of new methods in the API he's calling into.

What do you think ? Is there a way to make it safe or should we stop using implicits this way ?

Dici
  • 25,226
  • 7
  • 41
  • 82
  • 5
    For what it's worth I've got [a small project](https://github.com/travisbrown/abstracted) that allows you to "forget" all the methods on an instance and only use extension methods (including ones with the same signatures as real methods). – Travis Brown Oct 02 '15 at 14:01
  • @TravisBrown interesting, thanks – Dici Oct 02 '15 at 14:09

1 Answers1

12

The behavior of the language with regard to implicit conversions is defined very clearly:

if one calls a method m on an object o of a class C, and that class does not support method m, then Scala will look for an implicit conversion from C to something that does support m.

http://docs.scala-lang.org/tutorials/FAQ/finding-implicits.html

In other words, an implicit conversion will never be applied to heyMan in the expression heyMan.hello if the (statically-known) class/trait of heyMan already defines the method hello—implicit conversions are tried only when you call a method that it doesn't already define.


To me, the correct behaviour should be that the implicit definition always shades the real definition, so that the user code is protected from the apparition of new methods in the API he's calling into.

Isn't the opposite case equally true? If the implicit conversion did take precedence, then the user would be in danger of their long-defined methods that have been around for 5 years suddenly being shadowed by a new implicit conversion in a new version of a library dependence.

This case seems much more insidious and difficult to debug than the case that a user's explicit definition of a new method takes precedence.


Is there a way to make it safe or should we stop using implicits this way ?

If it is really critical that you get the implicit behavior, maybe you should force the implicit conversion with an explicit type:

object Main extends App {
    val heyMan = new HeyMan

    implicit class ImplicitHeyMan(heyMan: HeyMan) {
        def hello = println("What's up ?")
    }

    heyMan.hello // prints Hello

    val iHeyMan: ImplicitHeyMan // force conversion via implicit
    iHeyMan.hello // prints What's up
}

From our (extended) conversation in the comments, it seems like you want a way to check that the underlying class won't define the method you're using through the implicit conversion.

I think Łukasz's comment below is right on—this is something you should catch in testing. Specifically, you could use ScalaTest's assertTypeError for this. Just try calling the method outside the scope of your implicit, and it should fail to type check (and pass the test):

// Should pass only if your implicit isn't in scope,
// and the underlying class doesn't define the hello method
assertTypeError("(new HeyMan).hello")
DaoWen
  • 32,589
  • 6
  • 74
  • 101
  • I know, that's what my code demonstrates. And that's bad, isn't it ? It means if I defined an implicit and wrote a massive amount of code using it, and the next version of the API introduces a method with the same signature and a different implementation, all my code is broken without a single compilation warning or error – Dici Oct 02 '15 at 13:14
  • `the user would be in danger of their long-defined methods that have been around for 5 years suddenly being shadowed by a new implicit conversion in a new version of a library dependence` sure, but it would be stupid to shade an already existing method. I was talking about the case when the implicit is defined safely when the method does not exist yet, but becomes unsafe later because the method is introduced. To me, this is clearly more dangerous. Another option would be to prevent the implicit definition from compiling when it shades a method – Dici Oct 02 '15 at 13:24
  • @Łukasz this can't be an excuse. The compiler should help you with as many runtime errors as possible. Why does the compiler prevent you from using a covariant type in a contravariant position ? You could just unit test it and it would be fine ! Nope, the compiler has to help you with potential runtime errors that can be detected statically – Dici Oct 02 '15 at 13:25
  • What you say is true, but considering the library can change it can not only be introduction of method with the same signature as your implicit one, but anything really can change and you will be safe from it if you test parts of it you are using, or at least your own added methods. – Łukasz Oct 02 '15 at 15:00
  • @Dici *"sure, but it would be stupid to shade an already existing method."* Let's say I'm using library XYZ v1.0. The author XYZ is in no way affiliated with me, and has no knowledge of my proprietary code. I update to XYZ v1.1 for some shiny new feature—but unbeknownst to me I also end up with a new implicit definition that (assuming your preferred behavior) now overrides my explicitly-defined methods. How is that any more/less stupid than the case described in your question. – DaoWen Oct 02 '15 at 15:00
  • @Dici *"I was talking about the case when the implicit is defined safely when the method does not exist yet, but becomes unsafe later because the method is introduced."* Now you are proposing that the compiler start keeping track of the **chronology** of all of your methods / implicits. How do you propose it figure out if the method was there first, or if it was the implicit that was there first? – DaoWen Oct 02 '15 at 15:03
  • @DaoWen Nope, I did not say that. Here would be the rule : *an implicit class cannot define a method with the same signature as a method defined on the class it is wrapping*. This way, the new version of the API would still break the user code but *compile-time*, and it would be easier and safer to fix. Does it make sense ? – Dici Oct 02 '15 at 15:46
  • @Dici - Yes, that makes more sense. However, implicit classes aren't _exclusively_ used as a way to add extension methods to existing classes. There may be cases when you legitimately want to override behaviors of the wrapped class. Simple examples might be `toString` and `equals`. – DaoWen Oct 02 '15 at 19:00
  • @DaoWen the conversation is becoming a bit too long but I'm not sure to follow you. You mean overriding like you did, by specifying *explicitly* the *implicit* conversion ? To me, it kind of breaks the point of implicits, and it could be achieved with a simple decorator. If that's not what you meant I don't follow you, because we've shown the real method is called by default even if there is an implicit implementation of the same method – Dici Oct 02 '15 at 19:08
  • @Dici - Implicits are also used to automatically convert arguments to methods, constructors, etc. Also, see my edit. – DaoWen Oct 02 '15 at 21:23
  • @DaoWen right (for implicits arguments). Funny addition too with your edit, although it's a bit hacky to me. Thanks for your answer and comments – Dici Oct 02 '15 at 21:26
  • Implicits used as describe above to enrich (or pimp) an API can be problematic as you all discuss. However, Implicits used for typeclasses are entirely different matter and safe. – Channing Walton Oct 03 '15 at 09:57