Haskell Netwire:电线

时间:2015-03-07 19:17:18

标签: haskell frp netwire

我正在尝试使用netwire软件包试图了解FRP,我有一个简单的问题。

从以下简单的电线开始,我能够每5秒(大约)发出一次事件

myWire :: (Monad m, HasTime t s) => Wire s () m a Float
myWire = timeF

myWire' :: (Monad m, HasTime t s) => Wire s () m a Int
myWire' = fmap round myWire

myEvent :: (Monad m, HasTime t s) => Wire s () m a (Event Int)
myEvent = periodic 5 . myWire'

这是非常好的和直接的,但我接下来要做的是将每个生成的事件映射到电线,然后我可以观看更新。我有一个累加器函数,如下所示:

eventList :: (Monad m, HasTime t s) 
            => Wire s () m a (Event [Wire s () m a Int])
eventList = accumE go [] . myEvent
  where go soFar x = f x : soFar
        f x = for 10 . pure x --> pure 0

然后我引入了一条新的线,它会在eventList开始触发事件之前禁止,如下所示:

myList :: (Monad m, HasTime t s) => Wire s () m a [Wire s () m a Int]
myList = asSoonAs . eventList

所以我从事件变成了包含电线列表的电线。最后,我介绍了一根导线来分步这些导线并产生一系列结果:

myNums :: (Monad m, HasTime t s) => Wire s () m [Wire s () m a Int] [Int]
myNums = mkGen $ \dt wires -> do
  stepped <- mapM (\w -> stepWire w dt $ Right undefined) wires
  let alive = [ (r, w) | (Right r, w) <- stepped ]
  return (Right (map fst alive), myNums)

myNumList :: (Monad m, HasTime t s) => Wire s () m a [Int]
myNumList = myNums . myList

最后,我有我的主要例程来测试它:

main = testWire clockSession_ myNumList

我期望看到的是一个不断增长的列表,其中列表中的每个元素将显示它的创建时间为10秒,之后元素将显示为零。我得到的是越来越多的静态值。例如,我希望在几个步骤后看到的是

[0]
[5, 0]
[10, 5, 0]
[15, 10, 0, 0]

等等。我实际看到的是

[0]
[5, 0]
[10, 5, 0]
[15, 10, 5, 0]

所以我知道我的累加器功能正常工作:创建的每个事件都被转换成电线。但是我没有看到的是这些导线随着时间的推移会发出不同的值。我的陈述for 10 . pure x --> pure 0应该在时间结束后将它们切换为发出0。

我还是FRP的新手,所以我可能从根本上误解了一些重要的事情(可能就是这种情况。)

2 个答案:

答案 0 :(得分:2)

问题是从事件生成的导线不是持久的。类型Wire s e m a b的给定值实际上是函数的实例,它从类型b的值生成类型a的值。由于Haskell使用不可变值,因此为了分步连接,您必须使用stepWire生成的导线执行某些操作,否则您将获得相同输入的相同输出。看一下myList的结果:

Event 1: [for 10 . pure 0 --> pure 0]
Event 2: [for 10 . pure 5 --> pure 0, for 10 . pure 0 --> pure 0]
Event 3: [for 10 . pure 10 --> pure 0, for 10 . pure 5 --> pure 0, for 10 . pure 0 --> pure 0]
... etc

当您逐步执行这些连线时,您每次只会获得[.., 10, 5, 0],因为您正在重复使用for 10 . pure x --> pure 0电线的原始值。查看stepWire的签名:

stepWire :: Monad m => Wire s e m a b -> s -> Either e a -> m (Either e b, Wire s e m a b)

这意味着对于诸如

之类的陈述
(result, w') <- stepWire w dt (Right underfined)

...下次需要调用w'时,应使用stepWire,因为它是下一个实例的行为。如果你有一根产生电线的电线,那么你需要将生产的电线卸载到某处,以便它们可以单独加工。

对于(我相信)为您提供所需行为的程序,请参阅this code

$ ghc -o test test.hs
[1 of 1] Compiling Main             ( test.hs, test.o )
Linking test ...
$ ./test
[0]
[5,0]
[10,5,0]
[15,10,0,0]
[20,15,0,0,0]
...

答案 1 :(得分:2)

作为Mokosha suggested,我们可以保持我们之前已经知道的线路状态,并介绍最新事件的线路。例如,如果我们已经知道没有事件,并获得一个包含一根电线的列表,我们应该使用新的电线。

      [] -- we already know about
w0' : [] -- we got as input
w0' : [] -- what we should keep

如果我们已经知道一根电线或电线并发现更多电线,我们需要保留我们已经知道的电线并添加我们刚刚发现的新电线。

            w2  : w1  : w0  : [] -- we already know about
w4' : w3' : w2' : w1' : w0' : [] -- we got as input
w4' : w3' : w2  : w1  : w0  : [] -- what we should keep

从列表的前端更容易定义。第一个参数将是结果的前缀。如果剩下第二个参数,我们会将它们添加到列表的末尾。

makePrefixOf :: [a] -> [a] -> [a]
makePrefixOf [] ys = ys
makePrefixOf xs [] = xs
makePrefixOf (x:xs) (_:ys) = x:makePrefixOf xs ys

我们可以通过反转输入和输出为列表的后端定义相同的内容。第一个参数将是结果的后缀,如果第二个文章有任何额外的,它们将被添加到列表的前面。

makeSuffixOf :: [a] -> [a] -> [a]
makeSuffixOf xs ys = reverse $ makePrefixOf (reverse xs) (reverse ys)

现在我们可以实施myNums跟踪我们已经拥有哪个oldWires

myNums :: (Monad m, HasTime t s) => Wire s () m [Wire s () m a b] [b]
myNums = go []
  where
    go oldWires = mkGen $ \dt newWires -> do
        let wires = makeSuffixOf oldWires newWires
        stepped <- mapM (\w -> stepWire w dt $ Right undefined) wires
        let alive = [ (r, w) | (Right r, w) <- stepped ]
        return (Right (map fst alive), go (map snd alive))

如果我们想要迂腐,我们应该使用Maybe电线列表,这样当电线不再存在时,我们可以在其位置留下Nothing以便电线列表仍然匹配。如果我们这样做,即使有一个聪明的状态表示,我们将在电线死亡时泄漏空间。他们的原始定义仍将在eventList累积的列表中。

这给出了

的所需输出
            [ 0]
         [ 5, 0]
      [10, 5, 0]
   [15,10, 0, 0]
[20,15, 0, 0, 0]