我该如何正确组织这个基于 Rx 的反应式状态机?
How do I properly organize this Rx-based reactive state machine?
请注意: 尽管我在示例中使用了 Swift-lang,但我鼓励您尝试帮助我,即使您了解 RxJava 和 rxjs 或任何其他语言Rx 实现。
问题:我有一个状态机,是命令式写的。我想使用 Rx 进行响应式重写,但我正在努力想出正确的方法。
当前命令式系统如下所示:
var state: State
var metaState: MetaState
func updateMetaStateIfNeeded() {
if Bool.random() {
metaState = MetaState()
}
}
func tick() -> State {
updateMetaStateIfNeeded()
let newState = State(currentState: state, metaState: metaState)
self.state = newState
return newState
}
到目前为止我想到了什么:
let tick = PublishSubject<Void>()
let metaState = BehaviorSubject<MetaState>()
let state = BehaviorSubject<State>()
let tickAfterMetaStateUpdate = PublishSubject<Void>()
tick -> withLatestFrom metaState -> map to new metaState if update needed or the old one -> put result into metaState AND tickWithUpdatedMetaState
tickAfterMetaStateUpdate -> withLatestFrom state AND metaState -> map State(currentState: state, metaState: metaState) -> put result into state
(The users of this API subscribe to state subject, which will be updated every tick)
我对我得出的这个结果不满意。我想知道是否可以使用 scan
或 reduce
重写它,这样就没有使用 Subjects 并且它是一个普通的 Observable。
@Daniel T. 回答后更新:
哇,这真是正确的解决方案!我已经准备好分析:
- 有2个状态机,一个用于
metaState
更新,一个用于state
更新
- 他们每个人都有自己的开始状态,我们不需要指定
metaState
的输入字母表是 Void
,只是一个勾号。对于state
,它是第一个状态机 的metaState
输出
因此,有了这种理解,我使用 2 次扫描重写了系统:
let state = tick
.scan(MetaState()) { currentMetaState, void in
if Bool.random() {
return MetaState()
}
return currentMetaState
}
.scan(State()) { currentState, currentMetaState in
State(currentState: state, metaState: metaState)
}
WINRAR! Mind_blown.jpg!实际上,现在回想起来似乎很明显...:) 谢谢,谢谢,谢谢!
嗯...是的,scan
是实现 state machine 的首选运算符。为此,您需要一组有限的状态(通常实现为结构,但也可以是枚举),一个起始状态(通常使用 State 结构上的默认构造函数实现),一个输入字母表(通常实现为枚举,通常称为事件、命令或动作的某些变体)和一个转换函数(传递给 scan
的闭包。)
所以这里真正的问题是改变状态的 commands/actions 是什么?大多数时候,操作是由用户操作产生的,但您的问题并没有真正说明操作是什么,输入字母表是什么...
遗憾的是,问题中没有足够的信息来提供比这更多的细节。
--更新--
抱歉,您的更新不正确。传递给 scan
的闭包应该是纯的,而第一个肯定不是。如果 MetaState.init()
是纯的,则 Bool.random()
不是。
唯一应该将不纯的闭包传递给 Observable 的时候是在创建新的 Observable、使用最终结果时,或者在 do
.
中
对您所拥有内容的简单修复如下所示:
let state = tick
.flatMap { // this closure is an Observable factory. You can do impure things here.
Observable.just(Bool.random() ? MetaState() : nil)
}
.scan(State()) { currentState, metaState in // this closure should be pure.
if let metaState = metaState {
return State(currentState: currentState, metaState: metaState)
}
else {
return currentState
}
}
这确实是一个小改动,但它更清楚地显示了代码的意图,并明确了哪些可以进行单元测试,哪些不能进行单元测试。
请注意: 尽管我在示例中使用了 Swift-lang,但我鼓励您尝试帮助我,即使您了解 RxJava 和 rxjs 或任何其他语言Rx 实现。
问题:我有一个状态机,是命令式写的。我想使用 Rx 进行响应式重写,但我正在努力想出正确的方法。
当前命令式系统如下所示:
var state: State
var metaState: MetaState
func updateMetaStateIfNeeded() {
if Bool.random() {
metaState = MetaState()
}
}
func tick() -> State {
updateMetaStateIfNeeded()
let newState = State(currentState: state, metaState: metaState)
self.state = newState
return newState
}
到目前为止我想到了什么:
let tick = PublishSubject<Void>()
let metaState = BehaviorSubject<MetaState>()
let state = BehaviorSubject<State>()
let tickAfterMetaStateUpdate = PublishSubject<Void>()
tick -> withLatestFrom metaState -> map to new metaState if update needed or the old one -> put result into metaState AND tickWithUpdatedMetaState
tickAfterMetaStateUpdate -> withLatestFrom state AND metaState -> map State(currentState: state, metaState: metaState) -> put result into state
(The users of this API subscribe to state subject, which will be updated every tick)
我对我得出的这个结果不满意。我想知道是否可以使用 scan
或 reduce
重写它,这样就没有使用 Subjects 并且它是一个普通的 Observable。
@Daniel T. 回答后更新:
哇,这真是正确的解决方案!我已经准备好分析:
- 有2个状态机,一个用于
metaState
更新,一个用于state
更新 - 他们每个人都有自己的开始状态,我们不需要指定
metaState
的输入字母表是Void
,只是一个勾号。对于state
,它是第一个状态机 的
metaState
输出
因此,有了这种理解,我使用 2 次扫描重写了系统:
let state = tick
.scan(MetaState()) { currentMetaState, void in
if Bool.random() {
return MetaState()
}
return currentMetaState
}
.scan(State()) { currentState, currentMetaState in
State(currentState: state, metaState: metaState)
}
WINRAR! Mind_blown.jpg!实际上,现在回想起来似乎很明显...:) 谢谢,谢谢,谢谢!
嗯...是的,scan
是实现 state machine 的首选运算符。为此,您需要一组有限的状态(通常实现为结构,但也可以是枚举),一个起始状态(通常使用 State 结构上的默认构造函数实现),一个输入字母表(通常实现为枚举,通常称为事件、命令或动作的某些变体)和一个转换函数(传递给 scan
的闭包。)
所以这里真正的问题是改变状态的 commands/actions 是什么?大多数时候,操作是由用户操作产生的,但您的问题并没有真正说明操作是什么,输入字母表是什么...
遗憾的是,问题中没有足够的信息来提供比这更多的细节。
--更新--
抱歉,您的更新不正确。传递给 scan
的闭包应该是纯的,而第一个肯定不是。如果 MetaState.init()
是纯的,则 Bool.random()
不是。
唯一应该将不纯的闭包传递给 Observable 的时候是在创建新的 Observable、使用最终结果时,或者在 do
.
对您所拥有内容的简单修复如下所示:
let state = tick
.flatMap { // this closure is an Observable factory. You can do impure things here.
Observable.just(Bool.random() ? MetaState() : nil)
}
.scan(State()) { currentState, metaState in // this closure should be pure.
if let metaState = metaState {
return State(currentState: currentState, metaState: metaState)
}
else {
return currentState
}
}
这确实是一个小改动,但它更清楚地显示了代码的意图,并明确了哪些可以进行单元测试,哪些不能进行单元测试。