FRP 中 EventStreams 的循环依赖
Circular dependencies of EventStreams in FRP
所有示例都使用 Ramda
作为 _
(很清楚示例上下文中的方法)和 kefir
作为 frp
(几乎相同 API 如 bacon.js)
我有一个描述位置变化的流。
var xDelta = frp
.merge([
up.map(_.multiply(1)),
down.map(_.multiply(-1))
])
.sampledBy(frp.interval(10, 0))
.filter();
当我按下 UP
键时,它发出 +1
,在 DOWN
上发出 -1
。
获得位置 I scan
这个增量
var x = xDelta
.scan(_.add)
.toProperty(0);
这是预期的工作。但我想将 x
的值从 0
限制为 1000
。
为了解决这个问题,我找到了两个解决方案:
改变scan
中的函数
var x = xDelta.scan(function (prev, next) {
var newPosition = prev + next;
if (newPosition < 0 && next < 0) {
return prev;
}
if (newPosition > 1000 && next > 0) {
return prev;
}
return newPosition;
}, 0);
看起来还可以,但是以后随着新规则的出台,这个方法会越来越多。所以我的意思是它看起来不可组合和 FRPy。
我有 current
个职位。并且 delta
。我想将 delta
应用到 current
,前提是 current after applying
不会超出限制。
current
取决于 delta
delta
取决于 current after applying
current after applying
取决于 current
所以它看起来像循环依赖。但是我使用 flatMap
.
解决了它
var xDelta = frp
.merge([
up.map(_.multiply(1)),
down.map(_.multiply(-1))
])
.sampledBy(frp.interval(10, 0))
.filter();
var possibleNewPlace = xDelta
.flatMap(function (delta) {
return x
.take(1)
.map(_.add(delta));
});
var outOfLeftBoundFilter = possibleNewPlace
.map(_.lte(0))
.combine(xDelta.map(_.lte(0)), _.or);
var outOfRightBoundFilter = possibleNewPlace
.map(_.gte(1000))
.combine(xDelta.map(_.gte(0)), _.or);
var outOfBoundFilter = frp
.combine([
outOfLeftBoundFilter,
outOfRightBoundFilter
], _.and);
var x = xDelta
.filterBy(outOfBoundFilter)
.scan(_.add)
.toProperty(0);
您可以在 iofjuupasli/capture-the-sheep-frp
查看完整的代码示例
它正在运行演示 gh-pages
有效,但使用循环依赖可能是反模式。
有没有更好的方法解决FRP中的循环依赖?
第二个更一般的问题
使用 Controller
可以从两个 Model
中读取一些值,并根据它的值更新它们。
所以依赖关系看起来像:
---> Model
Controller ---|
---> Model
FRP 没有 Controller
。所以 Model
值应该从其他 Model
中以声明方式计算。但是,如果 Model1
从另一个相同的 Model2
计算,那么 Model2
从 Model1
计算呢?
Model ----->
<----- Model
例如两个具有碰撞检测的玩家:两个玩家都有 position
和 movement
。而第一个玩家的 movement
取决于第二个玩家的 position
,反之亦然。
我在这方面还是个新手。经过多年的命令式编码,开始以声明式 FRP 风格思考并不容易。可能我遗漏了什么。
using circular dependencies is probably anti-pattern
是也不是。从你实现它时遇到的困难,你可以看出很难创建循环依赖。特别是以声明的方式。但是,如果我们想使用纯声明式风格,我们可以看到循环依赖是无效的。例如。在 Haskell 中,您可以声明 let x = x + 1
- 但它会评估为异常。
current
depends on delta
, delta
depends on current after applying
,
current after applying
depends on current
如果你仔细观察,它并没有。如果这是一个真正的循环依赖,current
就没有任何价值。或者 threw an exception.
相反,current
取决于其先前的状态 。这是 FRP 中众所周知的模式,stepper。摘自 this answer:
e = ((+) <$> b) <@> einput
b = stepper 0 e
在不知道 <$>
和 <@>
究竟做什么的情况下,您大概可以说出事件 e
和行为 ("property") b
是如何依赖的关于事件 einput
。更好的是,我们可以声明性地扩展它们:
e = ((+) <$> bound) <@> einput
bound = (min 0) <$> (max 1000) <$> b
b = stepper 0 e
这基本上就是培根在 scan
中所做的。不幸的是,它迫使你在一个回调函数中完成所有这些。
我在任何 JS FRP 库中都没有看到 stepper
函数1。在 Bacon and Kefir 中,如果要实现此模式,您可能必须使用 Bus
。 我很高兴被证明是错误的:-)
[1]:好吧,除了我自己实现的那个(它还不能展示)。但是使用 Stepper
仍然感觉像在跳圈,因为 JavaScript 不支持递归声明。
有一个名为 cyclejs 的新 framework/library 完全符合您描述的循环机制,但在那种情况下是类似于 Facebook 的新 React 的网络前端库。
基本思想是拥有一个模型,它是一个 "state" 值流,一个呈现这些值的视图流,一个用户交互流,它发出来自视图副作用的用户交互(浏览器 DOM) 和一个 "intent" 流,它从用户那里创建高级事件并馈送到创建新值的模型中。
它仍处于早期开发阶段,但它是一个非常巧妙的想法并且目前运行良好。
所有示例都使用 Ramda
作为 _
(很清楚示例上下文中的方法)和 kefir
作为 frp
(几乎相同 API 如 bacon.js)
我有一个描述位置变化的流。
var xDelta = frp
.merge([
up.map(_.multiply(1)),
down.map(_.multiply(-1))
])
.sampledBy(frp.interval(10, 0))
.filter();
当我按下 UP
键时,它发出 +1
,在 DOWN
上发出 -1
。
获得位置 I scan
这个增量
var x = xDelta
.scan(_.add)
.toProperty(0);
这是预期的工作。但我想将 x
的值从 0
限制为 1000
。
为了解决这个问题,我找到了两个解决方案:
改变
中的函数scan
var x = xDelta.scan(function (prev, next) { var newPosition = prev + next; if (newPosition < 0 && next < 0) { return prev; } if (newPosition > 1000 && next > 0) { return prev; } return newPosition; }, 0);
看起来还可以,但是以后随着新规则的出台,这个方法会越来越多。所以我的意思是它看起来不可组合和 FRPy。
我有
current
个职位。并且delta
。我想将delta
应用到current
,前提是current after applying
不会超出限制。current
取决于delta
delta
取决于current after applying
current after applying
取决于current
所以它看起来像循环依赖。但是我使用
解决了它flatMap
.var xDelta = frp .merge([ up.map(_.multiply(1)), down.map(_.multiply(-1)) ]) .sampledBy(frp.interval(10, 0)) .filter(); var possibleNewPlace = xDelta .flatMap(function (delta) { return x .take(1) .map(_.add(delta)); }); var outOfLeftBoundFilter = possibleNewPlace .map(_.lte(0)) .combine(xDelta.map(_.lte(0)), _.or); var outOfRightBoundFilter = possibleNewPlace .map(_.gte(1000)) .combine(xDelta.map(_.gte(0)), _.or); var outOfBoundFilter = frp .combine([ outOfLeftBoundFilter, outOfRightBoundFilter ], _.and); var x = xDelta .filterBy(outOfBoundFilter) .scan(_.add) .toProperty(0);
您可以在 iofjuupasli/capture-the-sheep-frp
查看完整的代码示例它正在运行演示 gh-pages
有效,但使用循环依赖可能是反模式。
有没有更好的方法解决FRP中的循环依赖?
第二个更一般的问题
使用 Controller
可以从两个 Model
中读取一些值,并根据它的值更新它们。
所以依赖关系看起来像:
---> Model
Controller ---|
---> Model
FRP 没有 Controller
。所以 Model
值应该从其他 Model
中以声明方式计算。但是,如果 Model1
从另一个相同的 Model2
计算,那么 Model2
从 Model1
计算呢?
Model ----->
<----- Model
例如两个具有碰撞检测的玩家:两个玩家都有 position
和 movement
。而第一个玩家的 movement
取决于第二个玩家的 position
,反之亦然。
我在这方面还是个新手。经过多年的命令式编码,开始以声明式 FRP 风格思考并不容易。可能我遗漏了什么。
using circular dependencies is probably anti-pattern
是也不是。从你实现它时遇到的困难,你可以看出很难创建循环依赖。特别是以声明的方式。但是,如果我们想使用纯声明式风格,我们可以看到循环依赖是无效的。例如。在 Haskell 中,您可以声明 let x = x + 1
- 但它会评估为异常。
current
depends ondelta
,delta
depends oncurrent after applying
,current after applying
depends oncurrent
如果你仔细观察,它并没有。如果这是一个真正的循环依赖,current
就没有任何价值。或者 threw an exception.
相反,current
取决于其先前的状态 。这是 FRP 中众所周知的模式,stepper。摘自 this answer:
e = ((+) <$> b) <@> einput
b = stepper 0 e
在不知道 <$>
和 <@>
究竟做什么的情况下,您大概可以说出事件 e
和行为 ("property") b
是如何依赖的关于事件 einput
。更好的是,我们可以声明性地扩展它们:
e = ((+) <$> bound) <@> einput
bound = (min 0) <$> (max 1000) <$> b
b = stepper 0 e
这基本上就是培根在 scan
中所做的。不幸的是,它迫使你在一个回调函数中完成所有这些。
我在任何 JS FRP 库中都没有看到 stepper
函数1。在 Bacon and Kefir 中,如果要实现此模式,您可能必须使用 Bus
。 我很高兴被证明是错误的:-)
[1]:好吧,除了我自己实现的那个(它还不能展示)。但是使用 Stepper
仍然感觉像在跳圈,因为 JavaScript 不支持递归声明。
有一个名为 cyclejs 的新 framework/library 完全符合您描述的循环机制,但在那种情况下是类似于 Facebook 的新 React 的网络前端库。
基本思想是拥有一个模型,它是一个 "state" 值流,一个呈现这些值的视图流,一个用户交互流,它发出来自视图副作用的用户交互(浏览器 DOM) 和一个 "intent" 流,它从用户那里创建高级事件并馈送到创建新值的模型中。
它仍处于早期开发阶段,但它是一个非常巧妙的想法并且目前运行良好。