以无状态方式处理输入事件
Handle input events in stateless manner
昨天我问过如何以函数式方式处理键盘输入(使用 ScalaFX)。感谢@alfilercio 的帮助,我想到了这样的事情:
class InputHandler {
private val goUp: Int => State => State = step => state => State(state.x, state.y - step)
private val goDown: Int => State => State = step => state => State(state.x, state.y + step)
private val goLeft: Int => State => State = step => state => State(state.x - step, state.y)
private val goRight: Int => State => State = step => state => State(state.x + step, state.y)
private val doNothing: Int => State => State = step => state => state
private var behaviour: Int => State => State = doNothing
def keyPressedHandler(key: KeyEvent): Unit = {
behaviour = key.getCode match {
case KeyCode.Up.delegate => goUp
case KeyCode.Down.delegate => goDown
case KeyCode.Left.delegate => goLeft
case KeyCode.Right.delegate => goRight
case _ => doNothing
}
}
def keyReleasedHandler(key: KeyEvent): Unit = behaviour = doNothing
def update: Int => State => State = behaviour
}
然后有一个更新程序(工作名称),它根据经过的时间更新状态,一些内部逻辑 and/or 用户输入:
def update(state: State)(implicit inputHandler: InputHandler): State = { ... }
采用这种方法,核心 类 可以保持纯净,不需要任何单一变量。但是 InputHandler 本身仍然存在问题。我的意思是行为变量使它有状态。此 InputHandler 为用于生成 GUI 的 ScalaFX 添加了某种抽象。 metdods keyPressedHandler/keyRelasedHandler 分别设置为 ScalaFX 场景事件处理程序。总而言之,我正在寻找从此 InputHandler 中删除状态变量(行为)的方法。出于教育原因,我试图掌握功能方法,这就是为什么我一直用这个案例来打扰你:)
就我个人而言,我会假设所有 Listener
和 Handler
根据定义都是来自我们纯净世界之外的不纯净物体,所以 如果我想让事物保持纯净,我会让他们通过一些 IO 将命令作为值发送。
class SthListener(keyPressed: KeyCode => Unit,
keyReleased: KeyCode => Unit,
updateState: (State => Unit) => Unit) externds XListener {
def keyPressedHandler(key: KeyEvent): Unit = keyPressed(key.getCode)
def keyReleasedHandler(key: KeyEvent): Unit = keyReleased()
def update: Unit = updateState { state => // received from outside world
// how to update current component basing on received state
}
}
和其他地方
sealed trait Event
object Event {
case class KeyPressed(keyCode: Int) extends Event
case class KeyReleased(keyCode: Int) extends Event
}
val eventBus = Queue[Task, Event]
val stateRef = Ref[Task, State]
// translate pure operations on boundary of dirtyness
new SthListener(
keyPressed = keyCode => eventBus.enqueue1(Event.KeyPressed(keyCode)).runSync, // or runAndForget, or what works for you,
keyReleased = keyCode => eventBus.enqueue1(Event.KeyReleased(keyCode)).runSync,
update => stateRef.get.runSync
)
// handle state transitions
queue.dequeue
.evalMap {
case Event.KeyPressed(key) => stateRef.update(...)
case Event.KeyReleased(key) => stateRef.update(...)
}
.compile
.drain
总是这样做有意义吗?我个人不这么认为,但是 you/your 团队看到了纯度、RT 的很多价值,并且您有很多用例可以证明这种开销是合理的,那么您可以添加这种包装器以确保必要的API 不会强制您更改整个应用程序的编码风格,而只是强制您更改命令式风格的部分。
如果这太多了……好吧,您现在可以只使用可变性和命令式风格,等您感觉更熟悉后再重新审视该方法。不强求,慢慢来写当前你能看懂和维护的代码。
昨天我问过如何以函数式方式处理键盘输入(使用 ScalaFX)。感谢@alfilercio 的帮助,我想到了这样的事情:
class InputHandler {
private val goUp: Int => State => State = step => state => State(state.x, state.y - step)
private val goDown: Int => State => State = step => state => State(state.x, state.y + step)
private val goLeft: Int => State => State = step => state => State(state.x - step, state.y)
private val goRight: Int => State => State = step => state => State(state.x + step, state.y)
private val doNothing: Int => State => State = step => state => state
private var behaviour: Int => State => State = doNothing
def keyPressedHandler(key: KeyEvent): Unit = {
behaviour = key.getCode match {
case KeyCode.Up.delegate => goUp
case KeyCode.Down.delegate => goDown
case KeyCode.Left.delegate => goLeft
case KeyCode.Right.delegate => goRight
case _ => doNothing
}
}
def keyReleasedHandler(key: KeyEvent): Unit = behaviour = doNothing
def update: Int => State => State = behaviour
}
然后有一个更新程序(工作名称),它根据经过的时间更新状态,一些内部逻辑 and/or 用户输入:
def update(state: State)(implicit inputHandler: InputHandler): State = { ... }
采用这种方法,核心 类 可以保持纯净,不需要任何单一变量。但是 InputHandler 本身仍然存在问题。我的意思是行为变量使它有状态。此 InputHandler 为用于生成 GUI 的 ScalaFX 添加了某种抽象。 metdods keyPressedHandler/keyRelasedHandler 分别设置为 ScalaFX 场景事件处理程序。总而言之,我正在寻找从此 InputHandler 中删除状态变量(行为)的方法。出于教育原因,我试图掌握功能方法,这就是为什么我一直用这个案例来打扰你:)
就我个人而言,我会假设所有 Listener
和 Handler
根据定义都是来自我们纯净世界之外的不纯净物体,所以 如果我想让事物保持纯净,我会让他们通过一些 IO 将命令作为值发送。
class SthListener(keyPressed: KeyCode => Unit,
keyReleased: KeyCode => Unit,
updateState: (State => Unit) => Unit) externds XListener {
def keyPressedHandler(key: KeyEvent): Unit = keyPressed(key.getCode)
def keyReleasedHandler(key: KeyEvent): Unit = keyReleased()
def update: Unit = updateState { state => // received from outside world
// how to update current component basing on received state
}
}
和其他地方
sealed trait Event
object Event {
case class KeyPressed(keyCode: Int) extends Event
case class KeyReleased(keyCode: Int) extends Event
}
val eventBus = Queue[Task, Event]
val stateRef = Ref[Task, State]
// translate pure operations on boundary of dirtyness
new SthListener(
keyPressed = keyCode => eventBus.enqueue1(Event.KeyPressed(keyCode)).runSync, // or runAndForget, or what works for you,
keyReleased = keyCode => eventBus.enqueue1(Event.KeyReleased(keyCode)).runSync,
update => stateRef.get.runSync
)
// handle state transitions
queue.dequeue
.evalMap {
case Event.KeyPressed(key) => stateRef.update(...)
case Event.KeyReleased(key) => stateRef.update(...)
}
.compile
.drain
总是这样做有意义吗?我个人不这么认为,但是 you/your 团队看到了纯度、RT 的很多价值,并且您有很多用例可以证明这种开销是合理的,那么您可以添加这种包装器以确保必要的API 不会强制您更改整个应用程序的编码风格,而只是强制您更改命令式风格的部分。
如果这太多了……好吧,您现在可以只使用可变性和命令式风格,等您感觉更熟悉后再重新审视该方法。不强求,慢慢来写当前你能看懂和维护的代码。