我目前正在编写一个基于Arrows的FRP库(即timeless)。但是,我遇到了一个问题:
如果我在箭头中包含IO
操作,(在这种情况下为Signal s IO a b
,这是一个Kleisli箭头),我想拍摄一个"快照"最终返回值,而不是每次都运行操作。例如,我有一个涉及读取文件和解析为某些数据结构的操作,并且当前此操作正在运行每个更新帧。我尝试了一些使用Haskell的懒惰评估,以防止它一次又一次地运行,但它没有用。
从概念上讲,Signal
基本上(但不完全是)
a -> IO (b, Signal)
每次更新时,信号本身都会被新信号取代。现在,我想如果我使用类型IO
提供IO a
操作(使用Kleisli箭头),我可以某种方式将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
读取文件&#34; Test.txt&#34;。它每次评估。我想找到一种只评估一次的方法,并保留价值。
unsafePerformIO
可能有用,但顾名思义,它可能是“不安全的”#34;所以我不想使用它
答案 0 :(得分:1)
好的,我想我通过添加这个信号来实现它:
onceSwitch = mkPureN $ (\_ -> (Just (), mkEmpty))
我将切换推广到以下函数(并添加到Prefab
的{{1}}):
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
的API 会随着我的发展而剧烈变化,但我在下面使用的功能很可能不会改变。无论如何,同样的事情适用于timeless
,这是netwire
的起源,并有一些细微的变化。如果您需要制作一些应用程序,请立即使用它。
timeless