我目前正在编写一个基于Arrows(即
timeless)的FRP库.但是,我遇到了一个问题:
如果我在箭头内部包含一个IO动作(在这种情况下是信号的IO a b,这是一个Kleisli箭头),我想拍摄最终返回值的“快照”,而不是每次都运行动作.例如,我有一个涉及读取文件和解析为某些数据结构的操作,并且当前此操作正在运行每个更新帧.我尝试了一些使用Haskell的懒惰评估,以防止它一次又一次地运行,但它没有用.
从概念上讲,信号基本上(但不完全是)
a -> IO (b, Signal)
每次更新时,信号本身都被新信号取代.现在,我想如果我使用类型IO a in(使用Kleisli箭头)提供IO动作,我可以用某种方式替换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 of 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 -< ()
似乎工作有意. (更新:现在使用happenFor)
调整后,它看起来像这样.请注意,随着我的发展,永恒的API会发生剧烈的变化,但我在下面使用的功能可能不会改变.无论如何,同样的事情适用于netwire,这是永恒的起源,有一些微小的变化.如果您需要制作一些应用程序,请立即使用它.
{-# 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