-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Disallow subtypes of Function1 acting as implicit conversions #2065
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! This is something I've wanted ever since I saw http://scalapuzzlers.com/#pzzlr-054
Feels good to slain another puzzler. I have added it as a test. |
Can we ban all implicit |
@lihaoyi The common usecase that this allow is def foo(...)(implicit ev: A => B) = {
implicit def conv(x: A): B = ev(x)
...
} But that would be pretty cumbersome. |
To satisfy that common use case, could we do something like trait Converter[A, B]{
def apply(a: A): B
}
implicit def convertable[A, B](a: A)(implicit c: Converter[A, B]): B = c.apply(a) In the standard library, and then let people write def foo[A, B](a: A)(implicit ev: Converter[A, B]) = {
val b: B = a // convertable(a)(ev)
} That should (I think?) have the same implicit conversion effect, while also replacing the use of EDIT: Perhaps another alternative, if we want to keep using implicit def convertable[A, B](a: A)(implicit c: A => B): B = c.apply(a) Then implicit conversions can always be method-driven as far as the compiler is concerned.; |
That's covered in the comment explaining the scheme under "throws out the baby with the bathwater". It would just revert to the status quo where every implicit value of some subtype of Function1 creates an implicit conversion. I like the |
The new test `falseView.scala` shows the problem. We might create an implicit value of some type that happens to be a subtype of Function1. We might now expect that this gives us an implicit conversion, this is most often unintended and surprising. See the comment in Implicits#discardForView for a discussion why we picked the particular scheme implemented here.
I introduced an error in the refactoring two commits ago.
3e7e073
to
aa2f907
Compare
Now only Scala2 mode treats Function1's as implicit conversions. Instead we introduce a new subclass ImplicitConverter of Function1, instances of which are turned into implicit conversions.
@lihaoyi The latest commit follows your suggestion. Thanks for making it! |
I think it's more elegant not to distinguish between implicit def or val. The way I see it, there are only implicit values. When something doesn't match the expected type, the compiler searches the implicit scope for an A => B. The fact that the def syntax works follows the same principle as eta expansion. |
Another reason why puzzler-054 happens is that implicit val is used for a common type. What would we lost to enforce that implicit val has to be a custom type unrelated to any common type defined by stdlib? In the puzzler it would require to define custom Deck type: case class Deck(cards: List[Card]) To make Deck a subtype of List[Card] I could define implicit conversion to select List[Card] from Deck. Which is insipired by embedded types in Golang minus inheritance. object Deck {
implicit def toListCard(deck: Deck): List[Card] = deck.cards
} Now it works just like type substitution by nominal subtyping. I can use Deck anywhere List[Card] is expected. Puzzler-54 changes to this and is gone. It is still convinient to use Deck just like it would be List[Card] itself or extend List[Card]: case class Card(number: Int, suit: String = "clubs") {
val value = (number % 13) + 1 // ace = 1, king = 13
def isInDeck(implicit deck: Deck) = deck contains this
}
case class Deck(cards: List[Card])
object Deck {
implicit def toListCard(deck: Deck): List[Card] = deck.cards
}
implicit val deck = Deck(List(Card(1, "clubs")))
implicit def intToCard(n: Int) = Card(n)
println(1.isInDeck) Interesting note is that this implicit conversion is completely invisible in Golang. implicit def toListCard(deck: Deck): List[Card] = deck.cards |
It is probably not good enough rule as a general one because it would need to cover libraries. I have to think more how suggested limitations relates to it. |
In puzzler-54 I would expect compiler to fail with ambiguous resolution. What is the reason that it prefers implicit conversion defined by val over def? |
I'm not sure I follow. lihaoyi was suggesting to disallow vals acting as conversions and allow only defs. How it impacts the above use case? Currently it accepts def conversion directly as ev, no need to define it as implicit val. It would be disallowed by lihaoyi suggestion as well? As I understand it wouldn't be allowed to ETA expand def to function val during implicit search? Or however it works behind the wheels. If I understand it correctly at all. Why such restriction has to be there? Aren't function vals which are obtained by compiler from user defined implicit defs fine for implicit conversions? I can't figure out how it can go bad if only implicit defs (never vals) are considered during implicit conversion search and the code below would continue to work just like it works now. class Foo { def m = 1 }
class Bar { def m = 2 }
implicit def fooToBar(foo: Foo): Bar = new Bar
def baz()(implicit ev: Foo => Bar): Int = {
val foo = new Foo
val bar: Bar = ev(foo)
bar.m // returns 2
} One can forget to actually convert using // ... the same as before
def baz()(implicit ev: Foo => Bar): Int = {
val foo = new Foo
val bar: Bar = foo
bar.m // returns 2
} EDIT:
They are not implicit conversion, they have to be used explicitly. |
@vladap This works in Scala 2 currently, without any supporting code: object Test {
def foo[A,B](a: A)(implicit ev: A => B) = {
val b: B = a
}
} This works because |
@smarter. You use type parameters, hence it is cleaner. Otherwise I used concrete types Foo, Bar and in this case it is not obvious if it is rewritten using It could be better if it would fail at compilation, enforcing to explicitly use ev(a). Interesting point to realize is that the code below doesn't use implicit conversion. The conversion itself is actually explicit. def baz(foo: Foo)(implicit ev: Foo => Bar) = {
val bar: Bar = ev(foo) // this is explicit conversion
} |
What I think confuses me is if this suggested change to disallow subtypes of Function1 actually disallows Function1 itself as well to act as implicit conversion. EDIT: I think this doesn't make sense. Function1 can't act itself as implicit conversion. |
I have confirmed by decompilation that it indeed replaces Is it possible to create some puzzler based on it? At first sight it seems that end result is the same in both cases and it can boil down to a cosmetic. But I just can't get rid of a code smell feeling. |
def foo(...)(implicit ev: A => B) = {
implicit def conv(x: A): B = ev(x)
...
} I beg to differ. It is exactly what I would enforce. I would make it inconvenient to discourage using it. If anybody passes in an argument ev: A => B, I expect that it will be used inside the body. It is totally confusing if it is not. It is basically implicitly used implicit. Trying to make it more concise by not having to write ev(x) explicitly is exactly the kind of confusing code. There is very little gain because we are defining a function which itself makes code more concise at some other level. I write it once but can read many times. If anybody thinks that in some corner case he needs to avoid writing ev(x), I would like to see a real code where it makes a difference, he can define its own local implicit conversion. It should be strongly discouraged for general code nevertheless. Past 3 years I programmed in Scala, Java, Javascript. I like Scala the most out of them. And I always miss it when programming in other too. But there is one thing I'm getting tired after 3 years. I call it Scala forensics. Understanding a feature sometimes rises multiple questions how it interacts with other features. As you can see I had to decompile code to ensure how it works, now I can hope that it is deterministic. I don't have an idea where to find it in specs. Typically I find out that it is thought out well and it makes sense in the end. But I would aim to decrease number of rules one has to remember if there is not a clear gain. EDIT: The code above says to me. I want to implement this function in terms of implicit value. The next line says, no I changed my mind I actually want to use implicit conversion. It itself suggests that it is confusing. The fact that input argument is implicit shouldn't convey I want to hide it in a function implementation. It should just say that it is passed in implicitly. |
@lihaoyi Doesn't this suggestion actually enforce it? Which val or object which is not subtype of Function1 can be still considered as implicit conversion? Function1 itself can't act as conversion itself because it is a trait with abstract apply, I would have to make it implicit val or object first I believe. |
@vladap As has already been said to you, this is an issue tracker, not a discussion forum, you shouldn't be posting tons of long comments one after another. I'm not even sure what you're arguing for here, this merged PR does what you want: |
I couldn't know that to understand impact of this issue I have to create separate topic on scala-contributors. Other communities don't have a problem to discuss on Github. I have been told but I already started here at the same time and didn't want to offload it in the middle. Maybe I went off-board, I admit. But maybe if community would answer my questions and kindly ask to use SC next time it would be much better. This way I feel quite discouraged. For sure I could be more concise and up to a point if after 3 years in Scala I would know that there is a compiler "feature" which implicitly applies my implicit value if it is Function1. I always use implicit values explicitly. I don't see a reason I wouldn't in general code. If I want implicit conversion I define |
Implicit conversions and implicit values, e.g. used for DI, have very different requirements. It is why they don't work well together as implicits in Scala 2.x. Scala 2.x have them inter-connected. This issue solves one side that implicit val can be used as implicit conversion. And there is loop-hole in a compiler which implicitly uses implicit val as implicit conversion. The second one still remains. It is shielded through indirections to pass puzzler but I think it doesn't make code any easier. EDIT: It can be seen in puzzler where |
@vladap: this is a merged PR, while we appreciate your input - unless it is actionable we would rather you discuss it on the aforementioned forums meant for such discussion. If at a later point, the discussion turns into something actionable please open an issue. |
The new test
falseView.scala
shows the problem. We might createan implicit value of some type that happens to be a subtype of Function1.
We might not expect that this gives us an implicit conversion, this
is most often unintended and surprising.
See the comment in Implicits#discardForView for a discussion why
we picked the particular scheme implemented here.