haskell – 我可以为IO Monad拍摄“快照”吗?

我目前正在编写一个基于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
点赞