scalapuzzlers / scalapuzzlers.github.com

Github Pages behind scalapuzzlers.com
www.scalapuzzlers.com
161 stars 53 forks source link

Created puzzler: What's in a box. #127

Closed tbvh closed 9 years ago

tbvh commented 9 years ago

Tricks you on a method '->', by application of the ArrowAssoc implicit conversion. Probably works with 2.9.0 (and older), but tested on 2.11.4.

demobox commented 9 years ago

@tbvh: Hi Tim

Thanks for this! I get the following when running this in the REPL:

scala> :paste
// Entering paste mode (ctrl-D to finish)
case class Box(i: Int)

object Box {
  def ->(i: Int) = Box(i)
}
// Exiting paste mode, now interpreting.

defined class Box
defined object Box

scala> val myBox = new Box(5)
myBox: Box = Box(5)

scala> val yourBox = 5 -> Box
yourBox: (Int, Box.type) = (5,Box$@1e4c301d)

scala> val hisBox = Box -> 5
hisBox: Box = Box(5)

I'm guessing that's what your expecting. My main question is: what do you regard as the main element of the puzzler?

To me, the most potentially confusing line is val yourBox = 5 -> Box, because it's not clear why this compiles, unless there is another operator -> available that is not the one defined on Box. Do you think readers are likely to think this is also an application of Box.->? If so, how do you imagine a reader would come to that conclusion?

I'm asking because, if we think it's likely that the reader will either think that 5 -> Box does not compile, or that they will think that this is a different -> operator, it's not that strange that 5 -> Box returns something different from Box -> 5. As far as I'm understanding this, the difference between 5 -> Box and Box -> 5 is only really puzzling if you assume that it's the same operator.

@nermin: Your thoughts..?

tbvh commented 9 years ago

Thanks for your reply.

I encountered this puzzle IRL, when I was looking at HList. I didn't really know about right binding methods at that time. As I was experimenting with them, I implemented them using -> instead of the :: method, and everything still compiled (well, for 2 elements). It didn't really occur to me that -> had some completely different effect.

On the other hand, I do understand your point, perhaps equallity is too obvious. I was looking for an operation on Tuple, that I could implement on Box, so that I could call the (apparent) Box method on my Tuple object and raise the confusion. But I didn't find an appropriate method, other that equals. With your concern I can give it another try.

I have two suggestions

  1. I can change the constructor parameter to a val named x, and check equality of these fields. Tuple apparently also has an 'x' val defined, by another implicit.
  2. I can change the constructor parameter to a val named _1, and check equality of those fields (which would be equal).

For 1, equality would still fail, but we are accessing a member x on yourBox, which might add to the confusion. For 2, equality would hold. However, by using a field _1 it might be hinting in the direction of Tuples too much.

Let me know what you think.

demobox commented 9 years ago

@tbvh: Thank you for the update. Been a busy week here, but I hope to be able to get round to responding in the next couple of days.

Thanks for your patience! ;-)

demobox commented 9 years ago

I have two suggestions

Thanks for the suggestions, @tbvh! As you also pointed out, the _1 option really looks like a tuple, so personally I think the other one is perhaps a bit better. Perhaps something along the following lines:

scala> :paste
// Entering paste mode (ctrl-D to finish)

case class Point(x: Int, y: Int)
object Point {
  def ->(x: Int, y: Int = 0) = apply(x, y)
}

// Exiting paste mode, now interpreting.

defined class Point
defined object Point

scala> println((5 -> Point).x == ((5, 0) -> Point).x)
(5,$line3.$read$$iw$$iw$Point$@3aa0c203)

with candidate answers such as "does not compile", "prints true", "prints false", "throws an exception" or so.

The reason I'm still in two minds about this is because it fundamentally relies on the fact that the reader simply doesn't know the -> operator and/or doesn't know the rules about right-associativity of operators. I wouldn't be surprised if there are plenty of Scala developers that fall into that category, but an unofficial rule for puzzlers is that they should not be "confusing just because you don't know Scala".

The fact that this actually compiles (because a tuple happens to have a value x) is, to me, the surprising element of this example, and I think that is something that would not be an expected bit of knowledge for a relatively proficient Scala developer. But I'd be interested to hear what @nermin, @som-snytt or @dgruntz have to say on that topic.

som-snytt commented 9 years ago

Not very puzzling.

The day one of fast track to Scala has maps Map("Paris" -> "France") so basically everyone knows about arrow magic. It's how you learn what an implicit is.

But a puzzler on the DSL, 42 -> Box "put in a box" and val Box -> i = b "unbox it" which runs afoul of Predef implicits would be interesting.

Candidate implicits undergo overload resolution. Is there a way to make it prefer the Predef arrow conversion?

One can construct puzzlers like crosswords, but it's more fun to stumble across them IRL as the OP puts it.

demobox commented 9 years ago

@tbvh Any thoughts on A.P. Marki's comments? I'm inclined to agree that we need to find a reason for the puzzler that is not just that users don't know about ->. They would either have to have good reason to assume that the default -> should not apply (but then, for some reason, it does), or that is does apply when, in fact, it does not.

@som-snytt: Thoughts on the following?

scala> :paste
// Entering paste mode (ctrl-D to finish)

case class Point(x: Int, y: Int)
object Point {
  def ->(xy: (Int, Int)) = apply(xy._1, xy._2)
}

// Exiting paste mode, now interpreting.

scala> println((Point -> 3 -> 5).x)
(($line34.$read$$iw$$iw$Point$@576be180,3),5)

I guess the associativity of the arrows shouldn't really be a surprise - the odd thing perhaps is this even compiles, i.e. that Tuple2 has a field x. Still, I'm not sure that's much of a "lesson learned."

@nermin: Your thoughts on this one..?

som-snytt commented 9 years ago

@demobox That's pretty good. They've done work to stanch the members leaking in from implicits.

This is from Tuple2Zipped.Ops, and it's not even implicit class C(val x) extends AnyVal. It's just, Oops, should that be private?

Although it's kind of a bug, it's such a common occurrence that it's more of a syndrome. So that's a useful lesson. I wonder if IntelliJ tells you what introduced the member.

Actually, it's in the scaladoc:

http://www.scala-lang.org/files/archive/nightly/2.11.x/api/2.11.x/index.html#scala.Tuple2@x:%28T1,T2%29

I just fixed a bug with that -- oh yeah, it failed if the implicit took a by-name arg.

OK, why not, https://issues.scala-lang.org/browse/SI-9382

tbvh commented 9 years ago

Thank you for this discussion. I was just about to agree that the puzzle hinges too much on the lack of understanding about implicit ArrowAssoc (although I'm happy with the lessons I learned because of that). So I would have retracted my pull request. Now that it became leverage to fix some leaky behaviour, I'm happy to hand it over to your merits.

I hope to be able to return one day with a valid puzzler. (For puzzlers sake of course, not to pick on Scala.)

demobox commented 9 years ago

for puzzlers sake of course, not to pick on Scala

I suspect there's a conjecture in there somewhere that every Turing-complete language has puzzlers, or so ;-)

Thanks for sticking with this one, @tbvh - let's see what @nermin has to say!

som-snytt commented 9 years ago

Next puzzlers presentation should have an animation depicting a dart board with a photo of Martin. When the audience is stumped, a dart lands dead center, bull's-eye. As more people answer correctly or groan at how stupid the puzzler is, the dart flies wider of the mark.

demobox commented 9 years ago

@tbvh: I've created an updated version based on the discussion here. Let's continue the discussion over at #131?