简单的替代库无功? (哈斯克尔) [英] Simpler alternative libs to Reactive? (Haskell)

查看:125
本文介绍了简单的替代库无功? (哈斯克尔)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我学习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 IORefs, 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提供的函数 runE​​ventHandler ,且用户写了一个处理程序接受列表事件和其转换为 IO()

My approach is that the lib provide a function runEventHandler, and the user writes a handler that accepts list of Events 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 Behaviors 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 $。这些介绍有状态,结合事件取值在一起,事件行为秒。

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 Events together, and convert between Events and Behaviors.

状态是由 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 Events and Behaviors. There are four GLUT events that the example uses - reshapeCallback, keyboardMouseCallback, idleCallback, and displayCallback. We will map these to Events and Behaviors.

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 IORefs 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 替换, angleSpeed​​Change 。这些转换 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 Events from IO and run IO actions in response to Events. We can make Behaviors from Events and poll Behaviors when Events 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 Events (using fromAddHandler) or Behaviors (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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆