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

问题描述 投票:0回答:1

我目前正在编写一个基于 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
可能会起作用,但是,正如其名称所示,它可能“不安全”,所以我不想使用它

haskell io monads frp arrow-abstraction
1个回答
1
投票

好的,我想我可以通过添加这个信号来让它工作:

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
© www.soinside.com 2019 - 2024. All rights reserved.