我想在Haskell中学习FRP,但要确定要使用的库有点困难.许多似乎是死亡的尝试,有些似乎已经复活(例如最近在Yampa的活动).
根据我的阅读,似乎FRP有两种"种类":一侧是推拉式FRP(如Reactive-banana),另一侧是箭头式FRP(如Yampa).似乎在弗兰和弗里斯时代曾经有过一些"经典的FRP",但我没有发现其中的任何近期活动.
这两个(或三个)FRP真的是根本不同的方法吗?
其中一个是过时的理论而另一个是"未来的东西"?
或者它们是否必须并行发展,以满足不同的目的?
我是否列出了每个类别中最突出的图书馆,还是有其他选择要考虑(Sodium,Netwire等)?
我终于在J. Abrahamson的评论中看到了Evan Czaplicki的讲话.这非常有趣,并帮助我澄清了一些事情.我强烈推荐给发现这个问题的人.
我刚刚开始研究Haskell中的功能反应编程世界,我想在反应环境中尝试GUI编程(使用gtk,因为实质性绑定).
现在,我一直在看Grapefruit,Reactive-Banana和Buster,我想要任何证明使用任何一个或其他包装的喜悦/恐惧.
我所拥有的只是这些初步的选择:
我不介意做一些FRP包的绑定给我自己,因为我对抽象的后端想法(Grapefruit有)有一种矛盾的感觉..
虽然我觉得类型理论很有意思,但我也很重视实际程序的简单编码,这似乎让grapfruit记录有点偏离..
也就是说,除非你们当中有些人说服我不这么认为:-)
我一直在比较拉动式FRP(即netwire)和推拉式FRP(即反应性 - 香蕉)在游戏的实施中.一个优于另一个有优势吗?我注意到的事情是:
IO
漂浮,这总是更好.我还错过了什么?
编辑,以减少基于意见:主要目标是尽可能具有表达/简洁的东西,没有时间泄漏.
更新:我发现Netwire的一个大问题是,似乎没有一种方便的方法来获得多个帧速率,如"修复你的时间步"一文中所述.
更新2:我解决这个问题的方法是让我的游戏模拟线返回一个Float -> IO ()
获取alpha值并使用该alpha插值的所有GL调用.理想情况下,我应该能够通过一个"绘制函数"传递给另一个线程MVar
并在该线程中运行它.男人,哈斯克尔太棒了.
更新3:在提出这个问题后的六个月里,我开发了一个基于Netwire 的简单渲染引擎,以及"实体组件编程"的概念.在这个过程中,我实际上没有发现使用FRP非常有用.Wire
在某些情况下,s 的表现力实际上已成为障碍.问题集中在对象的"身份"上.在定义Wire
类型的值时,它没有标识,即它可以在整个网络中多次重复使用,并且每次表示不同的物理事物.当你想要做一些像碰撞检测这样的事情并且无法遍历场景图时,这是一个巨大的痛苦,因为它不能在不融合成单根不透明线的情况下存在.在"玩具"示例中不会出现此问题,因为很容易准确指定哪些对象与哪些其他对象以哪种方式交互.我相信这是任何一种FRP的问题.
如果一些Haskell向导在这方面证明我错了,那将是很棒的,但我真的没有看到它的方法.此外,如果解释不是很好,我很抱歉,但如果不自己尝试,这并不容易理解.
在Classic FRP的最近实现中,例如反应性香蕉,存在事件流和信号,它们是阶梯函数(反应性香蕉称它们为行为,但它们仍然是阶梯函数).我注意到Elm只使用信号,并没有区分信号和事件流.此外,反应性香蕉允许从事件流转变为信号(编辑:并且它可以使用重新作用对行为采取行动'虽然不被认为是良好做法),这意味着理论上我们可以应用所有事件流通过首先将信号转换为事件流,应用然后再次转换,对信号/行为进行组合.因此,鉴于它通常更容易使用并只学习一个抽象,分离信号和事件流的优势是什么?在使用信号和转换所有事件流组合器以对信号进行操作时是否有任何损失?
编辑:讨论非常有趣.我自己讨论的主要结论是,行为/事件源既需要相互递归的定义(反馈),也需要输出依赖于两个输入(一个行为和一个事件源),但只在一个时产生一个动作.他们改变了(<@>).
该问题特定于具有物理和视觉成分(例如,游戏)的反应性香蕉和实时模拟.
根据Fix Your Timestep!设置游戏循环的理想方式(假设物理需要可重复),你需要在帧之间固定的时间步长.在考虑了许多真正的并发症之后,作者到达了这个游戏循环:
double t = 0.0;
const double dt = 0.01;
double currentTime = hires_time_in_seconds();
double accumulator = 0.0;
State previous;
State current;
while ( !quit )
{
double newTime = time();
double frameTime = newTime - currentTime;
if ( frameTime > 0.25 )
frameTime = 0.25; // note: max frame time to avoid spiral of death
currentTime = newTime;
accumulator += frameTime;
while ( accumulator >= dt )
{
previousState = currentState;
integrate( currentState, t, …
Run Code Online (Sandbox Code Playgroud) 这是一个使用反应香蕉库的Haskell FRP程序示例.我只是刚刚开始尝试使用Haskell,特别是对于FRP的含义并不是很清楚.我真的很感激对下面代码的批评
{-# LANGUAGE DeriveDataTypeable #-}
module Main where
{-
Example FRP/zeromq app.
The idea is that messages come into a zeromq socket in the form "id state". The state is of each id is tracked until it's complete.
-}
import Control.Monad
import Data.ByteString.Char8 as C (unpack)
import Data.Map as M
import Data.Maybe
import Reactive.Banana
import System.Environment (getArgs)
import System.ZMQ
data Msg = Msg {mid :: String, state :: String}
deriving (Show, Typeable)
type IdMap = Map String String
-- …
Run Code Online (Sandbox Code Playgroud) 假设我有一个事件触发器,我想在触发时做两件事.首先,我希望它更新某些行为的价值.其次,如果满足其他条件,我希望它使用更新的行为值触发另一个事件send_off.以代码形式表示,假设我有
trigger :: Event b
trigger = ...
updateFromTrigger :: b -> (a -> a)
updateFromTrigger = ...
conditionFromTrigger :: b -> Bool
conditionFromTrigger = ...
behavior :: Behavior a
behavior = accumB initial_value (updateFromTrigger <$> trigger)
send_off :: Event a
send_off = ?????? (filterE conditionFromTrigger trigger)
Run Code Online (Sandbox Code Playgroud)
然后问题是:我把什么放在?????? 以便send_off发送最新的行为值,我的意思是包含刚刚应用于它的触发器更新的值.
不幸的是,如果我理解正确,行为的语义是这样的,更新的值不会立即可用,所以我这里唯一的选择是复制工作并重新计算行为的更新值,以便我可以立即使用它在另一个事件中,即填写?????? 喜欢的东西
send_off =
flip updateFromTrigger
<$>
behavior
<@>
filterE conditionFromTrigger trigger
Run Code Online (Sandbox Code Playgroud)
现在,有一种感觉我可以通过使用Discrete而不是Behavior来立即使我 …
我们有这样的代码:
guiState :: Discrete GuiState
guiState = stepperD (GuiState []) $
union (mkGuiState <$> changes model) evtAutoLayout
evtAutoLayout :: Event GuiState
evtAutoLayout = fmap fromJust . filterE isJust . fmap autoLayout $ changes guiState
Run Code Online (Sandbox Code Playgroud)
你可以看到evtAutoLayout提供给guiState,它被输入到evtAutoLayout中 - 所以那里有一个循环.这是故意的.自动布局调整gui状态直到达到平衡,然后它返回Nothing,因此它应该停止循环.当然,新的模型改变可以再次启动它.
但是,当我们将它们组合在一起时,我们会在编译函数调用中遇到无限循环.即使autoLayout = Nothing,它仍会在编译期间导致堆栈溢出.
如果我删除了guiState中的union调用并删除了图片中的evtAutoLayout ...
guiState :: Discrete GuiState
guiState = stepperD (GuiState []) $ mkGuiState <$> changes model
Run Code Online (Sandbox Code Playgroud)
它工作正常.
有什么建议?
我正在尝试通过Heinrich Apfelmus的反应性香蕉来了解FRP ,与我所看到的其他相比,它似乎是一个文档齐全且简单的库.
但是,我无法绕过AddHandler类型.假设我想使用GLFW来获取鼠标按钮,以便我有类似的东西eMouseButton :: Event ()
.看看这些例子,似乎我不得不使用fromAddHandler,但我不知道如何组装该AddHandler
参数.我想我不得不以newAddHandler
某种方式使用,但是怎么样?
我想一个如何连接reactive-banana
到其他东西的例子wx
会有很大帮助.
对不起,我刚刚开始研究反应性香蕉和玻璃钢.
反应香蕉的作者根据我的建议制作了这个例子,他创造了一个可以增加和减少的计数器.他使用累积函数来累积事件.我想我能够在某种程度上了解事件类型,并能够用它测试很多东西,但后来我记得还有行为.我调查了一下,但似乎这种行为意味着在类似的情况下使用; 修改现有变量,就像accumE对事件一样.
行为意味着什么,它的用例是什么?