Слушатели событий с продолжениями Scala

Предположим, мне нужно написать некоторый код графического интерфейса следующим образом:

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

Как бы вы написали это в стиле CPS, используя продолжения Scala?


person Michael    schedule 19.05.2011    source источник
comment
Собственно, это плагин компилятора, который преобразует ваш код в форму CPS. Ваш код должен быть написан в прямом стиле — для этого и нужна поддержка продолжения Scala. Я полагаю, это то, что вы имели в виду?   -  person Przemek Pokrywka    schedule 20.05.2011
comment
Этот код будет добавлять нового слушателя к виджету2 каждый раз, когда происходит событие1. Таким образом, в четвертый раз widget2 добавит трех слушателей к widget3!   -  person nafg    schedule 06.01.2012


Ответы (3)


Просто хотел предоставить рабочий пример в дополнение к другим ответам. С продолжениями Scala это может выглядеть так:

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

Этот код на самом деле компилируется и запускается, так что вы можете попробовать его сами. Вы должны получить следующий вывод:

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

Если вы будете компилировать этот пример, не забудьте включить продолжения, указав аргумент -P:continuations:enable для компилятора Scala.

person tenshi    schedule 19.05.2011

Смысл наличия продолжений заключается в возможности использовать прямой стиль кодирования, даже если обычно я был бы вынужден кодировать более сложным для использования способом (например, в стиле, управляемом событиями).

Таким образом, клиентский код, который я хотел бы написать, будет таким:

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

Так что вещи слушателя будут скрыты от меня. Но, конечно, слушатели все равно должны были быть где-то внизу. Я бы спрятал их в методе виджета waitForEvent() (либо добавленном в класс Widget, либо доступном через неявное преобразование). Метод будет выглядеть так:

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

Это хотя бы на концептуальном уровне. Чтобы заставить это работать, вам, возможно, потребуется добавить некоторые аннотации типа и/или @cps.

person Przemek Pokrywka    schedule 19.05.2011

Вот простой рабочий пример:

reset{
  shift { (k: Unit => Unit) => widget1 addListener(handle1 _ andThen k)}
  shift { (k: Unit => Unit) => widget2 addListener(handle2 _ andThen k)}
  widget3 addListener(handle3 _)
}
person Vasil Remeniuk    schedule 19.05.2011
comment
Разве вам не нужно что-то вроде shift { k => handle1(event1); k }? - person Aaron Novstrup; 20.05.2011
comment
Также обратите внимание, что сброс здесь на самом деле избыточен, так как сдвиг на самом деле не вызывается изнутри, хотя на первый взгляд может показаться, что это так. В этом примере сдвиг действительно вызывается тем, кто хочет запустить функцию обработки событий, и ни одна из строк этого не делает. - person Przemek Pokrywka; 20.05.2011
comment
Спасибо за предложения, я изменил фрагмент - person Vasil Remeniuk; 20.05.2011