Event listeners with Scala continuations
Asked Answered
S

3

5

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?

Septuagint answered 19/5, 2011 at 16:46 Comment(2)
Actually, this is the compiler plugin, that transforms your code to CPS form. Your code is supposed to be written in a direct style - that is what the Scala continuation support is for. I guess that is what you had in mind?Georganngeorge
That code will add a new listener to widget2 every time event1 occurs. So on the fourth time, widget2 will add three listeners to widget3!Scalade
R
8

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.

Robyn answered 19/5, 2011 at 21:53 Comment(0)
G
3

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.

Georganngeorge answered 19/5, 2011 at 21:18 Comment(0)
F
1

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 _)
}
Finecut answered 19/5, 2011 at 19:52 Comment(3)
Wouldn't you need something like shift { k => handle1(event1); k }?Mucous
Also note, that reset is actually redundant here, as shift is not really called from within of it, although it may appear so at first. In this example shift is really called by whoever fancies to run the event handling function, and none of the lines does it.Georganngeorge
Thanks for suggestions, I've modified the snippetFinecut

© 2022 - 2024 — McMap. All rights reserved.