1

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?

2 Answers 2

2

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).

Sign up to request clarification or add additional context in comments.

2 Comments

Good. Thanks for your insight that desugared implicit function only applied to the last line. But, listener-adding function is usually written by third-party, so we cannot modify it; If I can modify it, I will not use Runnable at all!.
So there is the { ... }.runnable, which in my option is even safer since it will prevent you applying the implicit in places in don't what. In my android apps i use a series of promote methods to do similar stuff.
0

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")
})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.