3

So, I was trying to "pimp" my Futures (among other things) a little bit with something like this:

implicit class Pimp[T](val x: T) extends AnyVal  {
  def ->>[R](f: T => R): R = f(x)
  def ->>[R](r: => R): R = r
  def <<-(f: T => Unit): T = f.andThen(_ => x).apply(x)
  def <<-(f: => Unit): T = <<- { _ => f }
}

For some reason, when I try to do this now:

Future(1) ->> { Await.result(_, 1 seconds) }

it fails with error: missing parameter type for expanded function ((x$1) => Await.result(x$1, 1.seconds))

But this works:

Future(1) <<- { Await.result(_, 1 seconds) }

So, the question is what is wrong with the first case. What am I doing wrong, and how to make it work. I even tried this (not at all what I want, because it is way too verbose): Future(1) --> { case x: Future[Int] => Awayt.result(_, 1 seconds) }, but even this fails for some reason (still says the parameter type is unknown - how is it unknown, if I explicitly specify it???)

I suspect, the difference between the first case and second is that ->> has a type param, while <<- does not ... But even this Future(1) ->>[Int] { Await.result(_, 1 seconds) } still does not work :(

Update Ok, so, I found one way in which it works:

Future(1) ->> { (f:Future[Int]) => Await.result(f, 1 seconds) }

This kinda defeats the purpose of my implicit though, because of how verbose it is :( Is there any way to make it infer the parameter type without spelling it out like it does in the other case?

Update 2 One more thing. Removing the overloaded "flavor" of ->> from Pimp makes the other case work. I.e., if I only leave

def ->>[R](f: T => R): R = f(x)

in the definition of Pimp, then

Future(1) ->> { Await.result(_, 1 seconds) }

beautifully works as intended.

Having thought about it for a while, I think, that the problem is that in the overloaded case foo ->> { _ => bar } is ambiguous: it could mean "return bar", or "return a function foo => bar". This does not explain however why foo ->>[Bar] { _ => bar } does not work.

Dima
  • 39,570
  • 6
  • 44
  • 70
  • `Future(1) ->> {f => Await.result(f, 1 seconds) }` would be correct, but the type inference can't handle that. – Reactormonk Mar 16 '16 at 16:47
  • The surprising part to me is that `<<-` works; leaving my understanding in a comment because it's clearly incomplete: Because there are overloads, there is no single "expected type", so the argument `{ Await.result(_, 1 seconds) }` needs to be typechecked on its own _before_ selecting an overload and in this case it obviously doesn't know the argument type. – Alexey Romanov Mar 16 '16 at 17:26
  • @AlexeyRomanov right, but `->>[Int]` should work then, because the expected type is unambigous, no? – Dima Mar 16 '16 at 17:30
  • See the updated answer. – Alexey Romanov Mar 16 '16 at 22:10
  • @AlexeyRomanov That flew right above my head :) But either way, I didn't mean to argue, this is not implemented according to the specification ... I am more like "why is specification not allowing it to infer the type in a case like this, when it is clear what it should be" :) FWIW, I was hoping that `->>[Int]` should make it work as if it was not overloaded ... – Dima Mar 16 '16 at 22:29

1 Answers1

0

You can shorten the call to Future(1) ->> { Await.result(_: Future[Int], 1 seconds) }. Not perfect, but better than your Update 1. Even better would be to avoid overloading in "pimped" methods: unsurprisingly, it complicates type inference.

After reading http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html carefully, it seems that specifying [Int] shouldn't work:

  1. The entire expression is a function application (6.6).

  2. Since Future(1).->> is overloaded, we go to 6.23.

    1. The set of alternatives A is the set of members referenced by ->>; [Int] isn't taken into account, because the section talks about "identifier or selection e", and the selection is Future(1).->>.

    2. shape({ Await.result(_, 1 seconds) }) is Any => Nothing.

    3. "Let B be the set of alternatives in A that are applicable to expressions (e1,…,en) of types (shape(e1),…,shape(en))". Both methods in A are applicable to Any => Nothing (as you say, "it could mean "return bar", or "return a function foo => bar"). So we go to "Otherwise, let S1,…,Sm be the vector of types obtained by typing each argument with an undefined expected type" and this typing fails.

    4. For the <<- case, => Unit is not applicable (because the expected type is not Unit at this step, value discarding doesn't happen).

But it's subtle enough that I've changed my mind twice while writing this update; I think I've got it right now.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • Yeah, I am removing the overloads, seems like it's "the lesser evil", although, it's unfortunate: my intent was to avoid typing things like `doSomething() ->> { _ => "Success" }` in favor of just `doSomething() ->> "Success"` :( – Dima Mar 16 '16 at 17:34