Haskell Netwire:电线的电线
Haskell Netwire: wires of wires
我正在研究 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 还是个新手,所以我可能从根本上误解了一些重要的事情(可能是这样。)
问题是事件生成的连线不是持久的。 Wire s e m a b
类型的给定值实际上是函数的 时间 实例,该函数从 a
类型的值生成 b
类型的值.由于 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]
...
与一样,我们可以保留我们从以前的事件中已知的连线状态,并引入最新事件中的连线。例如,如果我们已经不知道任何事件,并且得到一个包含一根电线的列表,我们应该使用新的电线。
[] -- 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
以便列表电线仍然匹配。如果我们这样做,即使对状态有一个巧妙的表示,我们也会在线路中断时泄漏 space。它们的原始定义仍将在 eventList
.
累积的列表中徘徊
这给出了
的期望输出
[ 0]
[ 5, 0]
[10, 5, 0]
[15,10, 0, 0]
[20,15, 0, 0, 0]
我正在研究 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 还是个新手,所以我可能从根本上误解了一些重要的事情(可能是这样。)
问题是事件生成的连线不是持久的。 Wire s e m a b
类型的给定值实际上是函数的 时间 实例,该函数从 a
类型的值生成 b
类型的值.由于 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]
...
与
[] -- 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
以便列表电线仍然匹配。如果我们这样做,即使对状态有一个巧妙的表示,我们也会在线路中断时泄漏 space。它们的原始定义仍将在 eventList
.
这给出了
的期望输出 [ 0]
[ 5, 0]
[10, 5, 0]
[15,10, 0, 0]
[20,15, 0, 0, 0]