Reputation: 1311
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
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
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