Question

Suppose I have to write some GUI code as follows:

widget1.addListener(event1 =>
   handle1(event1)
   widget2.addListener(event2 =>
     handle2(event2)
     widget3.addListener(event3 => handle3(event3))
   )
)

How would you write it in CPS-style using Scala continuations?

Was it helpful?

Solution

Just wanted to provide working example in addition to other answers. With Scala continuations it can look like this:

import scala.util.continuations._

object ContinuationsExample extends App {
  val widget1 = Widget()
  val widget2 = Widget()

  reset {
    val event1 = widget1.getEvent
    println("Handling first event: " + event1)
    val event2 = widget2.getEvent
    println("Handling second event: " + event2)
  }

  widget2 fireEvent Event("should not be handled")
  widget1 fireEvent Event("event for first widget")
  widget2 fireEvent Event("event for second widget")
  widget1 fireEvent Event("one more event")
}

case class Event(text: String)

case class Widget() {
  type Listener = Event => Unit
  var listeners : List[Listener] = Nil

  def getEvent = shift { (l: Listener) =>
    listeners = l +: listeners
  }

  def fireEvent(event: Event) = listeners.foreach(_(event))
}

This code actually compiles and runs, so you can try it yourself. You should receive following output:

Handling first event: Event(event for first widget)
Handling second event: Event(event for second widget)
Handling first event: Event(one more event) 

If you will compile this example, then don't forget to enable continuations by providing -P:continuations:enable argument for the Scala compiler.

OTHER TIPS

The point of having continuations is the ability to use a direct style of coding, even if normally I would be forced to code in a harder-to-use way (for example in an event-driven style).

So the client code I'd wish to be able to write would be like that:

reset {
    val event1 = widget1.waitForEvent()
    handle1(event1)
    val event2 = widget2.waitForEvent()
    handle2(event2)
    val event3 = widget3.waitForEvent()
    handle3(event3)
}

So listener stuff would be hidden from me. But of course, the listeners would still have to be somewhere underneath. I'd hide them in the widget's waitForEvent() method (either added to the Widget class or available through an implicit conversion). The method would look like:

def waitForEvent() = shift { k =>
    this.addListener(event => k(event))
    k
}

This is at least at the conceptual level. To get this to work you'd need to add some type- and/or @cps annotations probably.

Here's a simple working example:

reset{
  shift { (k: Unit => Unit) => widget1 addListener(handle1 _ andThen k)}
  shift { (k: Unit => Unit) => widget2 addListener(handle2 _ andThen k)}
  widget3 addListener(handle3 _)
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top