#scala #user-interface #continuations #continuation-passing
#scala #пользовательский интерфейс #продолжения #продолжение-передача
Вопрос:
Предположим, мне нужно написать некоторый графический код следующим образом:
widget1.addListener(event1 =>
handle1(event1)
widget2.addListener(event2 =>
handle2(event2)
widget3.addListener(event3 => handle3(event3))
)
)
Как бы вы написали это в стиле CPS, используя продолжения Scala?
Комментарии:
1. На самом деле, это плагин компилятора, который преобразует ваш код в форму CPS. Предполагается, что ваш код должен быть написан в прямом стиле — для этого и существует поддержка продолжения Scala. Я полагаю, это то, что вы имели в виду?
2. Этот код будет добавлять нового прослушивателя в widget2 каждый раз, когда происходит event1. Итак, в четвертый раз widget2 добавит трех прослушивателей к widget3!
Ответ №1:
Просто хотел предоставить рабочий пример в дополнение к другим ответам. С продолжениями 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.
Ответ №2:
Смысл наличия продолжений заключается в возможности использовать прямой стиль кодирования, даже если обычно я был бы вынужден кодировать более сложным в использовании способом (например, в стиле, управляемом событиями).
Итак, клиентский код, который я хотел бы иметь возможность написать, был бы таким:
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.
Ответ №3:
Вот простой рабочий пример:
reset{
shift { (k: Unit => Unit) => widget1 addListener(handle1 _ andThen k)}
shift { (k: Unit => Unit) => widget2 addListener(handle2 _ andThen k)}
widget3 addListener(handle3 _)
}
Комментарии:
1. Разве вам не нужно что-то вроде
shift { k => handle1(event1); k }
?2. Также обратите внимание, что reset на самом деле здесь избыточен, поскольку shift на самом деле не вызывается изнутри, хотя на первый взгляд это может показаться так. В этом примере shift действительно вызывается тем, кто хочет запустить функцию обработки событий, и ни одна из строк этого не делает.
3. Спасибо за предложения, я изменил фрагмент