我目前正在编写一个基于 Arrows 的 FRP 库(即 timeless)。然而,我遇到了一个问题:
如果我将
IO
操作包裹在箭头内(在本例中为 Signal s IO a b
,这是一个 Kleisli 箭头),我想拍摄最终返回值的“快照”,而不是每次都运行该操作。例如,我有一个涉及读取文件并解析为某些数据结构的操作,当前该操作正在运行每一帧更新。我尝试了一下利用 Haskell 的惰性求值来防止它一次又一次地运行,但它不起作用。
从概念上讲,
Signal
基本上(但不完全是)
a -> IO (b, Signal)
每次更新,信号本身都会被新信号替换。现在,我认为如果我在(使用 Kleisli 箭头)中提供类型为
IO
的 IO a
操作,我可以以某种方式用保存上一个操作的最终结果的其他内容替换 Signal
。但是,我找不到一种方法来做到这一点,因为我无法从 IO
中提取任何内容,并且简单地将信号替换为常量信号似乎并不能阻止重新评估操作。
这是一个最小的测试程序:
{-# LANGUAGE Arrows #-}
module Main where
import FRP.Timeless
import Debug.Trace
s1 :: (Monad m) => Signal s m a Int
s1 = mkConst $ trace "Signal 1" $ Just 5
s2 :: (Monad m) => Signal s m Int Int
s2 = arr $ trace "Signal 2" (+1)
s3 :: (Monad m) => Signal s m a ()
s3 = arr $ \_ -> ()
sc = mkKleisli_ $ \_ -> do
putStrLn "SC"
readFile "test.txt"
sp = mkKleisli_ putStrLn
box :: Signal s IO () ()
box = proc _ -> do
file <- sc -< ()
sp -< file
returnA -< ()
box2 = proc _ -> do
box -< ()
main = do
runBox clockSession_ box2
这里,
sc
读取文件“Test.txt”。每次都会对其进行评估。我想找到一种方法,只评估一次,并保持价值。
顺便说一句,
unsafePerformIO
可能会起作用,但是,正如其名称所示,它可能“不安全”,所以我不想使用它
好的,我想我可以通过添加这个信号来让它工作:
onceSwitch = mkPureN $ (\_ -> (Just (), mkEmpty))
我将开关概括为以下功能(并添加到
Prefab
的timeless
):
occursFor :: b -> Int -> Signal s m a b
occursFor b n
| n == 0 = mkEmpty
| n > 0 = mkPureN $ \_ -> (Just b, occursFor b $ n-1)
| otherwise = error "[ERROR] occursFor: Nothing occurs for less than zero times!"
第一次运行时输出为
()
,然后禁止,此信号:
onceIO = SGen $ f
where
f _ ma = return (ma, SArr $ const ma)
首次运行后将成为常数。像这样链接
IO
动作:
file <- onceIO <<< sc <<< () `occursFor` 1 -< ()
似乎可以正常工作。 (更新:现在使用
occursFor
)
调整后,看起来像这样。注意,随着我的开发,
timeless
will的API会发生剧烈的变化,但是我下面使用的功能很可能不会改变。无论如何,同样的事情也适用于 netwire
,这是 timeless
的起源,有一些细微的变化。如果您需要制作一些应用程序,请暂时使用它。
{-# LANGUAGE Arrows #-}
module Main where
import FRP.Timeless
import Debug.Trace
sc = mkKleisli_ $ \_ -> do
putStrLn "SC"
return "A"
sp = mkKleisli_ putStrLn
box :: Signal s IO () ()
box = proc _ -> do
file <- snapOnce <<< sc <<< inhibitsAfter 1 -< ()
sp -< file
returnA -< ()
box2 = proc _ -> do
box -< ()
main = do
runBox clockSession_ box2