pocorall
pocorall

Reputation: 1311

Scala: passing function parameter which does not have parameter

Observer pattern or callback is a widely used design pattern in Java. However, implementing callback interface with anonymous class is a real pain, so it is common in Scala that introducing implicit conversion for these callback class implementation. For example, implicit conversion for Runnable interface is:

implicit def func2Runnable[F](f: => F): Runnable =
  new Runnable {
    def run() {
      f
    }
  }

And suppose that there are some listener registry:

def addMyListener(m: Runnable) {
  // mock function for test
  for (i <- 1 to 2) {
    m.run()
  }
}

Then implicit conversion magically compacted my code:

addMyListener(println("Printed twice"))

Problem:

When I pass multi-line code block to addMyListener(), only the last line of code is passed to it:

addMyListener {
  println("This line is printed once")
  println("Only this is printed twice")
}

Known workaround:

I added a parenthesis on the conversion function:

implicit def func2Runnable[F](f:() => F): Runnable =
  new Runnable {
    def run() {
      f()
    }
  }

But it is verbose when we use it:

addMyListener(() => println("Printed twice"))
addMyListener {() =>
    println("This line is printed twice")
    println("This is also printed twice")
}

Is there a cleaner solution to expose this?

Upvotes: 1

Views: 920

Answers (2)

pocorall
pocorall

Reputation: 1311

Self answer:

I found both type of implicit conversion can be applied at the same time:

implicit def lazy2Runnable[F](f: => F): Runnable =
  new Runnable {
    def run() {
      f
    }
  }

implicit def func2Runnable[F](f: () => F): Runnable =
  new Runnable {
    def run() {
      f()
    }
  }

Although a little care should be taken when we write a multi-line block, resulting code is little bit improved:

addMyListener(println("Printed twice"))
addMyListener(() => {
  println("This line is printed twice")
  println("This is also printed twice")
})

Upvotes: 0

pedrofurla
pedrofurla

Reputation: 12783

Ok, some food for thought here. I added a second implicit conversion but this time it must be explicitly triggered. BTW, it only related to the part before the workaround.

implicit def func2Runnable2(f: => Unit) = new {
  def runnable = new Runnable {
    def run() {
      f
    }
  }
}

In REPL:

scala> addMyListener ({
     |   println("This line is printed once")
     |   println("Only this is printed twice")
     | }.runnable)
This line is printed once
Only this is printed twice
This line is printed once
Only this is printed twice

So, my theory is that the conversion of the first implicit is only happening in the last line. So it'd desugar to

addMyListener ({
  println("This line is printed once")
  func2Runnable(println("Only this is printed twice"))
})

and not the expected:

addMyListener(func2Runnable{
  println("This line is printed once")
  println("Only this is printed twice")
})

Let's test the two hypotheses:

scala> addMyListener ({
     |   println("This line is printed once")
     |   func2Runnable(println("Only this is printed twice"))
     | })
This line is printed once
Only this is printed twice
Only this is printed twice

scala> addMyListener(func2Runnable{
     |   println("This line is printed once")
     |   println("Only this is printed twice")
     | })
This line is printed once
Only this is printed twice
This line is printed once
Only this is printed twice

Yey! As I wanted to show :)

For even more thought on the issue, notice now that the argument Runnable is itself by-name:

def addMyListener2(m: => Runnable) {
  // mock function for test
  for (i <- 1 to 2) {
    m.run()
  }
}

scala> addMyListener2 ({
     |   println("This line is printed once")
     |   println("Only this is printed twice")
     | })
This line is printed once
Only this is printed twice
This line is printed once
Only this is printed twice

Speculating to what is happening: print(...) is Unit, both of them. { prints-inside } is also Unit. So it simply applies the implicit conversion of func2Runnable to the last argument of the block instead of the block itself.

Now the proper way to what see this is to start a REPL session with scala -Xprint:namer (maybe typer instead of namer).

Upvotes: 2

Related Questions