简单的替代库无功? (哈斯克尔) [英] Simpler alternative libs to Reactive? (Haskell)
问题描述
我学习Haskell,并尝试写一些事件驱动程序。
I'm learning Haskell, and trying to write some event-driven programs.
以下code是从教程: http://www.haskell.org/haskellwiki / OpenGLTutorial2
The following code is from the tutorial: http://www.haskell.org/haskellwiki/OpenGLTutorial2
main = do
(progname,_) <- getArgsAndInitialize
initialDisplayMode $= [DoubleBuffered]
createWindow "Hello World"
reshapeCallback $= Just reshape
angle <- newIORef (0.0::GLfloat) -- 1
delta <- newIORef (0.1::GLfloat) -- 2
position <- newIORef (0.0::GLfloat, 0.0) -- 3
keyboardMouseCallback $= Just (keyboardMouse delta position)
idleCallback $= Just (idle angle delta)
displayCallback $= (display angle position)
mainLoop
的状态存储在 IOREF
s,因此它看起来就像是势在必行的语言,这使得
The states are stored in IORef
s, which makes it looks just like imperative language.
我心中已经听说有比这个其他的API Graphics.UI.GLUT
,(例如:无
) ,但看起来很复杂。
I'v heard that there are APIs other than this Graphics.UI.GLUT
, (e.g. Reactive
), but it looks very complicated.
我的做法是,LIB提供的函数 runEventHandler
,且用户写了一个处理程序
接受列表事件
和其转换为 IO()
。
My approach is that the lib provide a function runEventHandler
, and the user writes a handler
that accepts list of Event
s and convert them to IO ()
.
handler :: [Event] -> IO ()
runEventHandler :: ( [Event] -> IO () ) -> IO ()
以及主
函数应该是这样的:
main = runEventHandler handler
有没有这样的库?
Is there such libs?
我使用多线程目前正在执行的,但我担心它可能是穷人的表现...
I am currently implementing one using multi-threading, but I'm worrying that it might be poor in performance ...
推荐答案
反应香蕉是一个成熟的库非常类似于反应。我们不会试图重塑一个FRP库;相反,我们将探索如何反应香蕉整合到我们自己的项目。
reactive-banana is a mature library very similar to reactive. We won't try to reinvent an frp library; instead we'll explore how to integrate reactive-banana into a project for ourselves.
要使用的功能反应编程库一样反应香蕉用OpenGL我们将分工成4个部分,其中2个已经存在。我们将使用现有的GLUT库与OpenGL的互动,而现有的无功香蕉库函数反应式编程的实现。我们将提供2个部分我们自己的。我们将提供第一部分是一个框架,GLUT连接到无功香蕉。我们将提供第二部分是将在玻璃钢实现(反应性香蕉)和框架和GLUT类型方面被写入的程序
To use a functional reactive programming library like reactive-banana with OpenGL we will divide the work into 4 parts, 2 of which already exist. We will use the existing GLUT library to interact with OpenGL, and the existing reactive-banana library for an implementation of functional reactive programming. We will provide 2 parts of our own. The first part we will provide is a framework that will connect GLUT to reactive-banana. The second part we will provide is the program that will be written in terms of the frp implementation (reactive-banana) and framework and GLUT types.
两者,我们提供将在反应性香蕉FRP库方面被写入的部件。该库有两个大思路,事件吨
和行为吨
。 事件吨
再携带类型的数据presents事件 A
在那个时间出现在不同的点。 行为吨
重新presents类型的时变值 A
是在所有时间点来定义。 ŧ类型参数,我们被类型系统需要preserve否则忽略。
Both of the parts that we provide will be written in terms of the reactive-banana frp library. The library has two big ideas, Event t a
and Behavior t a
. Event t a
represents events carrying data of type a
that occur at different points in time. Behavior t a
represents a time varying value of type a
that is defined at all points in time. The t
type argument we are required by the type system to preserve but otherwise ignore.
大部分的接口来事件
和行为
隐藏在它们的实例。 事件
是函子
- 我们能 FMAP
或&LT; $方式&gt;
过任何值的函数事件
Most of the interface to Event
and Behavior
are hidden in their instances. Event
is a Functor
- we can fmap
or <$>
a function over the values of any Event
.
fmap :: (a -> b) -> Event t a -> Event t b
行为
既是应用型
和函子
。我们可以通过 FMAP
或&LT; $&GT;
在所有值的函数行为
呈现,可以提供新的常量不变的价值观与纯粹
,并计算出新的行为
s的&LT; *方式&gt;
Behavior
is both Applicative
and a Functor
. We can fmap
or <$>
a function over all the values a Behavior
takes on, can provide new constant unchanging values with pure
, and calculate new Behavior
s with <*>
.
fmap :: (a -> b) -> Behavior t a -> Behavior t b
pure :: a -> Behavior t a
<*> :: Behavior t (a -> b) -> Behavior t a -> Behavior t b
有几<一href=\"http://hackage.haskell.org/package/reactive-banana-0.8.0.2/docs/Reactive-Banana-Combinators.html\">other通过反应香蕉的提供的功能提供功能,不能在基类型类而言psented重新$ P $。这些介绍有状态,结合事件
取值在一起,事件
和行为之间的转换code>秒。
There are a few other functions provided by reactive-banana that provide functionality that can't be represented in terms of base typeclasses. These introduce statefulness, combine Event
s together, and convert between Event
s and Behavior
s.
状态是由 accumE
介绍这需要从previous变化的初始值和事件
值到一个新的价值,并产生新的价值观的事件
。 accumB
生成一个行为
而不是
State is introduced by accumE
which takes an initial value and an Event
of changes from the previous value to a new value and produces an Event
of the new values. accumB
produces a Behavior
instead
accumE :: a -> Event t (a -> a) -> Event t a
accumB :: a -> Event t (a -> a) -> Behavior t a
联盟
将两个事件一起流
union :: Event t a -> Event t a -> Event t a
步进
可以在事件
转换为行为
保持最新的值,如果我们提供,以便它在时间上是在所有点上定义的一个初始值。 适用
或&LT; @&GT;
可以转换行为
到事件
如果我们提供一系列活动
在该轮询行为
。
stepper
can convert an Event
to a Behavior
holding the most recent value if we provide an initial value so that it is defined at all points in time. apply
or <@>
can convert a Behavior
into an Event
if we provide a series of Events
at which to poll the current value of the Behavior
.
stepper :: a -> Event t a -> Behavior t a
<@> :: Behavior t (a -> b) -> Event t a -> Event t b
为事件
和行为
和<一的情况下,19功能href=\"http://hackage.haskell.org/package/reactive-banana-0.8.0.2/docs/Reactive-Banana-Combinators.html\">Reactive.Banana.Combinators弥补功能反应式编程的整个界面。
The instances for Event
and Behavior
and the 19 functions in Reactive.Banana.Combinators make up the entire interface for functional reactive programming.
总体而言,我们需要通过我们正在实施OpenGL的例子中使用的GLUT库和库,无功香蕉库,活性香蕉出口使框架和RankNTypes扩展,线程间通信一对夫妇的机制,以及能够读取系统时钟。
Overall, we will need the GLUT library and libraries used by the OpenGL example we are implementing, the reactive-banana library, the reactive-banana exports for making frameworks and the RankNTypes extension, a couple mechanisms for interthread communication, and the ability to read the system clock.
{-# LANGUAGE RankNTypes #-}
import Graphics.UI.GLUT
import Control.Monad
import Reactive.Banana
import Reactive.Banana.Frameworks
import Data.IORef
import Control.Concurrent.MVar
import Data.Time
框架接口
我们的框架将地图从GLUT到反应香蕉事件
和 IO
事件>行为秒。有迹象表明,例如使用四个转运蛋白的活动 - reshapeCallback
, keyboardMouseCallback
, idleCallback
和 displayCallback
。我们将这些映射到事件
和行为
秒。
The framework interface
Our framework will map the IO
events from GLUT to reactive-banana Event
s and Behavior
s. There are four GLUT events that the example uses - reshapeCallback
, keyboardMouseCallback
, idleCallback
, and displayCallback
. We will map these to Event
s and Behavior
s.
reshapeCallback
当用户调整窗口大小时运行。作为一个回调,它需要的类型的东西键入ReshapeCallback =大小 - &GT; IO()
。我们会重新present这是一个事件牛逼大小
。
reshapeCallback
is run when the user resizes the window. As a callback, it required something of the type type ReshapeCallback = Size -> IO ()
. We will represent this as an Event t Size
.
keyboardMouseCallback
当用户提供了键盘输入,移动鼠标,或单击鼠标按钮时运行。作为一个回调,它需要的类型的东西键入KeyboardMouseCallback =键 - &GT; KeyState - &GT;改性剂 - &GT;位置 - &GT; IO()
。我们会重新present这与类型输入事件牛逼KeyboardMouse
,其中 KeyboardMouse
捆绑在一起的所有的参数传递给回调。
keyboardMouseCallback
is run when the user provides keyboard input, moves the mouse, or clicks a mouse button. As a callback, it required something of the type type KeyboardMouseCallback = Key -> KeyState -> Modifiers -> Position -> IO ()
. We will represent this as an input with type Event t KeyboardMouse
, where KeyboardMouse
bundles together all of the arguments passed to the callback.
data KeyboardMouse = KeyboardMouse {
key :: Key,
keyState :: KeyState,
modifiers :: Modifiers,
pos :: Position
}
idleCallback
当时间的推移运行。我们会重新present这个作为一个跟踪那已经过去了,行为牛逼DiffTime
时间量的行为。因为它是一个行为
而不是一个事件
,我们的程序将无法直接观察到时间的流逝。如果不想要的,我们可以使用事件
代替。
idleCallback
is run when time passes. We will represent this as a behavior that tracks the amount of time that has passed, Behavior t DiffTime
. Because it is a Behavior
instead of an Event
, our program won't be able to directly observe time passing. If this isn't desired, we could use an Event
instead.
捆绑所有的投入一起,我们得到
Bundling all of the inputs together we get
data Inputs t = Inputs {
keyboardMouse :: Event t KeyboardMouse,
time :: Behavior t DiffTime,
reshape :: Event t Size
}
displayCallback
是从其他不同的回调;它不是输入到节目,而是是用于输出需要显示什么。由于过剩,可能在任何时间运行这个尝试在屏幕上显示的东西,它使得它在所有时间点来定义感。我们会重新present以行为牛逼DisplayCallback此输出
。
displayCallback
is different from the other callbacks; it isn't for the input to the program, but instead is for outputting what needs to be displayed. Since GLUT could run this at any time to try to display something on the screen, it makes sense for it to be defined at all points in time. We will represent this output with a Behavior t DisplayCallback
.
还有一个输出我们需要 - 响应事件的例子程序偶尔会产生其他的IO操作。我们将允许程序引发事件与事件吨至执行任意IO(IO())
。
There is one more output we will need - in response to events the example program occasionally produces other IO actions. We will allow the program to raise events to execute arbitrary IO with an Event t (IO ())
.
都捆绑在一起的输出,我们得到
Bundling both outputs together we get
data Outputs t = Outputs {
display :: Behavior t DisplayCallback,
whenIdle :: Event t (IO ())
}
我们的框架将被传递给它一个程序FORALL t时键入调用。输入笔 - &GT;输出ŧ
。我们将定义程序
和 reactiveGLUT
在接下来的两部分。
Our framework will be invoked by passing it a program with the type forall t. Inputs t -> Outputs t
. We will define program
and reactiveGLUT
in the next two sections.
main :: IO ()
main = do
(progname,_) <- getArgsAndInitialize
initialDisplayMode $= [DoubleBuffered]
createWindow "Hello World"
reactiveGLUT program
程序
该计划将利用反应香蕉到输入
映射到输出
。要开始移植教程code,我们会从立方体删除
和重命名 IOREF
取值重塑
到 onReshape
,因为它与我们的框架接口的名称冲突。
The program
The program will use reactive-banana to map the Inputs
to the Outputs
. To get started porting the tutorial code, we'll remove the IORef
s from cubes
and rename reshape
to onReshape
since it conflicts with a name from our framework interface.
cubes :: GLfloat -> (GLfloat, GLfloat) -> DisplayCallback
cubes a (x',y') = do
clear [ColorBuffer]
loadIdentity
translate $ Vector3 x' y' 0
preservingMatrix $ do
rotate a $ Vector3 0 0 1
scale 0.7 0.7 (0.7::GLfloat)
forM_ (points 7) $ \(x,y,z) -> preservingMatrix $ do
color $ Color3 ((x+1)/2) ((y+1)/2) ((z+1)/2)
translate $ Vector3 x y z
cube 0.1
swapBuffers
onReshape :: ReshapeCallback
onReshape size = do
viewport $= (Position 0 0, size)
keyboardMouse
将完全由 positionChange
替换, angleSpeedChange
。这些转换 KeyboardMouse
事件转换为一个变化做出位置或在立方体的旋转速度。当需要为事件没有任何变化,他们返回没有
。
keyboardMouse
will be completely replaced by positionChange
and angleSpeedChange
. These convert a KeyboardMouse
event into a change to make to either the position or the speed the cubes are rotating. When no change is needed for an event, they return Nothing
.
positionChange :: Fractional a => KeyboardMouse -> Maybe ((a, a) -> (a, a))
positionChange (KeyboardMouse (SpecialKey k) Down _ _) = case k of
KeyLeft -> Just $ \(x,y) -> (x-0.1,y)
KeyRight -> Just $ \(x,y) -> (x+0.1,y)
KeyUp -> Just $ \(x,y) -> (x,y+0.1)
KeyDown -> Just $ \(x,y) -> (x,y-0.1)
_ -> Nothing
positionChange _ = Nothing
angleSpeedChange :: Num a => KeyboardMouse -> Maybe (a -> a)
angleSpeedChange (KeyboardMouse (Char c) Down _ _) = case c of
' ' -> Just negate
'+' -> Just (+1)
'-' -> Just (subtract 1)
_ -> Nothing
angleSpeedChange _ = Nothing
计算的位置是相当容易的,我们积累从键盘输入的变化。 filterJust ::活动吨(也许) - GT;事件吨
抛出,我们没有兴趣
positionB :: Fractional a => Inputs t -> Behavior t (a, a)
positionB = accumB (0.0, 0.0) . filterJust . fmap positionChange . keyboardMouse
我们会有点不同计算旋转立方体的角度。我们将记住的时间和角度时的速度变化,应用该计算角度在时间差的差的函数,并添加到初始角度
We'll calculate the angle of the rotating cubes a bit differently. We'll remember the time and angle when the speed changes, apply a function that calculates the difference in angle to the difference in times, and add that to the initial angle.
angleCalculation :: (Num a, Num b) => a -> b -> (a -> b) -> a -> b
angleCalculation a0 b0 f a1 = f (a1 - a0) + b0
计算角度
是比较困难的。首先我们计算一个事件, angleF ::活动吨(DiffTime - &GT; GLfloat)
,从向角之间的差时间之间的差值保持的功能。我们提升并应用我们的 angleCalculation
来的电流时间
和角度
和查询,在 angleF
事件的每次出现。我们转换轮询功能到行为
与步进
,并将其应用到当前时间
。
Calculating the angle
is a bit more difficult. First we compute an event, angleF :: Event t (DiffTime -> GLfloat)
, holding a function from a difference between times to a difference between angles. We lift and apply our angleCalculation
to the current time
and angle
, and poll that at each occurrence of the angleF
event. We convert the polled function into a Behavior
with stepper
and apply it to the current time
.
angleB :: Fractional a => Inputs t -> Behavior t a
angleB inputs = angle
where
initialSpeed = 2
angleSpeed = accumE initialSpeed . filterJust . fmap angleSpeedChange . keyboardMouse $ inputs
scaleSpeed x y = 10 * x * realToFrac y
angleF = scaleSpeed <$> angleSpeed
angleSteps = (angleCalculation <$> time inputs <*> angle) <@> angleF
angle = stepper (scaleSpeed initialSpeed) angleSteps <*> time inputs
整个程序
地图输入
到输出
。它说,出于什么显示
的行为是立方体
解除并应用到角度和位置。在事件
其他 IO
副作用 onReshape
每次在重塑
事件发生。
The whole program
maps Inputs
to Outputs
. It says that the behavior for what to display
is cubes
lifted and applied to the angle and position. The Event
for other IO
side effects is onReshape
every time the reshape
event happens.
program :: Inputs t -> Outputs t
program inputs = outputs
where
outputs = Outputs {
display = cubes <$> angleB inputs <*> positionB inputs,
whenIdle = onReshape <$> reshape inputs
}
框架
我们的框架,接受带有FORALL t时键入的程序。输入笔 - &GT;输出ŧ
并运行它。实施该框架中,我们使用 Reactive.Banana.Frameworks
的功能。这些功能使我们能够从 IO
筹集事件
和运行 IO
响应行动事件
秒。我们可以让行为
从事件
和调查行为
取值S当事件
取值出现使用函数从 Reactive.Banana.Combinators
。
The framework
Our framework accepts a program with the type forall t. Inputs t -> Outputs t
and runs it. To implement the framework, we use the functions in Reactive.Banana.Frameworks
. These functions allow us to raise Event
s from IO
and run IO
actions in response to Event
s. We can make Behavior
s from Event
s and poll Behavior
s when Event
s occur using the functions from Reactive.Banana.Combinators
.
reactiveGLUT :: (forall t. Inputs t -> Outputs t) -> IO ()
reactiveGLUT program = do
-- Initial values
initialTime <- getCurrentTime
-- Events
(addKeyboardMouse, raiseKeyboardMouse) <- newAddHandler
(addTime, raiseTime) <- newAddHandler
(addReshape, raiseReshape) <- newAddHandler
(addDisplay, raiseDisplay) <- newAddHandler
newAddHandler
创建一个用以谈论一个事件TA
把手和功能提升的情况下,键入 A - &GT; IO()
。我们为键盘和鼠标输入,闲置时间的推移,窗口形状变化明显的事件。我们也使我们将使用轮询显示事件
行为
当我们需要在<$运行C $ C> displayCallback 。
newAddHandler
creates a handle with which to talk about an Event t a
, and a function to raise the event of type a -> IO ()
. We make the obvious events for keyboard and mouse input, idle time passing, and the window shape changing. We also make an event that we will use to poll the display
Behavior
when we need to run it in the displayCallback
.
我们必须一一克服棘手的问题 - 的OpenGL要求所有的用户界面交互在一个特定的线程的情况发生,但我们不知道我们线程绑定到无功香蕉活动将在发生的行动,我们将使用跨线程共享,以确保输出
IO几个变量
在OpenGL的线程中运行。对于显示
输出,我们将使用的阻尼特性
来存储查询显示
的行动。对于在 whenIdle
排队,我们会在 IOREF 累加它们
IO
行动code>,
We have one tricky problem to overcome - OpenGL requires all the UI interaction to happen in a specific thread, but we aren't sure what thread the actions we bind to reactive-banana events will happen in. We'll use a couple of variables shared across threads to make sure the Output
IO
is run in the OpenGL thread. For display
output, we'll use an MVar
to store the polled display
action. For IO
actions that are queued in whenIdle
we'll accumulate them in an IORef
,
-- output variables and how to write to them
displayVar <- newEmptyMVar
whenIdleRef <- newIORef (return ())
let
setDisplay = putMVar displayVar
runDisplay = takeMVar displayVar >>= id
addWhenIdle y = atomicModifyIORef' whenIdleRef (\x -> (x >> y, ()))
runWhenIdle = atomicModifyIORef' whenIdleRef (\x -> (return (), x)) >>= id
我们的整个网络由以下几部分组成。首先,我们创建事件
S(使用 fromAddHandler
)或行为
S(使用 fromChanges
)的每个输入的
和事件
轮询输出显示
。我们执行处理少量以简化时钟。我们应用程序
到输入
我们prepared获得程序的输出
。使用&LT; @
,我们轮询显示
每当我们的显示事件发生。最后, reactimate
讲述反应香蕉运行 setDisplay
或 addWhenIdle
每当corresponsonding 事件
出现。一旦我们描述的网络,我们编译
和开动
了。
Our whole network consists of the following parts. First we create Event
s (using fromAddHandler
) or Behavior
s (using fromChanges
) for each of the Inputs
and an Event
for polling the output display
. We perform a small amount of processing to simplify the clock. We apply the program
to the inputs
we prepared to get the program's Outputs
. Using <@
, we poll the display
whenever our display event happens. Finally, reactimate
tells reactive-banana to run setDisplay
or addWhenIdle
whenever the corresponsonding Event
occurs. Once we have described the network we compile
and actuate
it.
-- Reactive network for GLUT programs
let networkDescription :: forall t. Frameworks t => Moment t ()
networkDescription = do
keyboardMouseEvent <- fromAddHandler addKeyboardMouse
clock <- fromChanges initialTime addTime
reshapeEvent <- fromAddHandler addReshape
displayEvent <- fromAddHandler addDisplay
let
diffTime = realToFrac . (flip diffUTCTime) initialTime <$> clock
inputs = Inputs keyboardMouseEvent diffTime reshapeEvent
outputs = program inputs
displayPoll = display outputs <@ displayEvent
reactimate $ fmap setDisplay displayPoll
reactimate $ fmap addWhenIdle (whenIdle outputs)
network <- compile networkDescription
actuate network
对于每一个过剩的回调,我们感兴趣的是我们提高相应的反应香蕉事件
。对于闲置的回调,我们还运行任何排队的事件。对于显示回调,我们运行调查 DisplayCallback
。
For each of the GLUT callbacks we are interested in we raise the corresponding reactive-banana Event
. For the idle callback we also run any queued events. For the display callback, we run the polled DisplayCallback
.
-- Handle GLUT events
keyboardMouseCallback $= Just (\k ks m p -> raiseKeyboardMouse (KeyboardMouse k ks m p))
idleCallback $= Just (do
getCurrentTime >>= raiseTime
runWhenIdle
postRedisplay Nothing)
reshapeCallback $= Just raiseReshape
displayCallback $= do
raiseDisplay ()
runDisplay
mainLoop
的实施例的其余
本教程code的其余部分可重复逐字
The rest of the example
The rest of the tutorial code can be repeated verbatim
vertex3f :: (GLfloat, GLfloat, GLfloat) -> IO ()
vertex3f (x, y, z) = vertex $ Vertex3 x y z
points :: Int -> [(GLfloat,GLfloat,GLfloat)]
points n = [ (sin (2*pi*k/n'), cos (2*pi*k/n'), 0) | k <- [1..n'] ]
where n' = fromIntegral n
cube :: GLfloat -> IO ()
cube w = renderPrimitive Quads $ mapM_ vertex3f
[ ( w, w, w), ( w, w,-w), ( w,-w,-w), ( w,-w, w),
( w, w, w), ( w, w,-w), (-w, w,-w), (-w, w, w),
( w, w, w), ( w,-w, w), (-w,-w, w), (-w, w, w),
(-w, w, w), (-w, w,-w), (-w,-w,-w), (-w,-w, w),
( w,-w, w), ( w,-w,-w), (-w,-w,-w), (-w,-w, w),
( w, w,-w), ( w,-w,-w), (-w,-w,-w), (-w, w,-w) ]
这篇关于简单的替代库无功? (哈斯克尔)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!