在 Haskell 中注册信号处理程序,并根据状态执行操作

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

我有一些函数

app :: StateT AppState IO ()
,它可以在进行大量计算和IO的同时维护一些应用程序状态(我已经定义了
main = void $ runStateT app initialState
)。我想注册一个信号处理程序
handler :: StateT AppState IO ()
,它可以通过编写类似的内容来查看和修改当前状态

installHandler sigINT (Catch handler) Nothing

这不起作用,因为

Catch :: IO () -> Handler
handler :: StateT AppState IO ()

我尝试过以下方法:

installHandler . Catch . pure <$> handler <*> pure Nothing

这行代码被评估后立即运行

handler
(因为我们使用的是
StateT
应用程序)。此外,
Catch . pure <$> handler
Catch . pure $ ()
相同,因为我们运行副作用来检索
()
,然后应用
pure
只是将其包装在
IO
中,而不捕获这些副作用。

有没有一种方法可以注册信号处理程序,以便它在不破坏

StateT
抽象的情况下实际执行操作?我将不胜感激任何建议!

另外,如果我使用“副作用”这个短语激怒了任何人,我很抱歉。

haskell functional-programming monad-transformers
1个回答
0
投票

根据 @chi 的评论,没有办法让这个精确的设计发挥作用。

信号是通过单独的 IO 线程处理的,因此实现这一点的最直接方法是通过应用程序 monad“已知”的共享可变引用,并通过处理函数的闭包提供。

如果处理程序只需要访问状态的一小部分,并且该小部分在应用程序的其余部分中没有太多使用,那么最简单的方法是向应用程序状态添加一个字段来存储

 IORef
MVar
到那一小块状态。

特别是,如果您只是尝试在处理程序中设置一个将在应用程序循环中“注意到”的标志,那么

IORef
就可以了。以下程序说明了一般方法:

{-# LANGUAGE OverloadedRecordDot #-}

import System.Posix.Signals
import Control.Concurrent
import Control.Monad.State
import Data.IORef

data AppState = AppState
  { count :: Int
  , flagINT :: IORef Bool
  }

app :: StateT AppState IO ()
app = do
  x <- gets count
  modify $ \s -> s{count=s.count+1}
  liftIO $ putStrLn $ "Running " ++ show x
  liftIO $ threadDelay 1000000
  quit <- gets flagINT >>= liftIO . readIORef
  if quit then do
    liftIO $ putStrLn "Quitting"
    else app

main = do
  flagINT_ <- newIORef False
  installHandler sigINT (Catch (writeIORef flagINT_ True)) Nothing
  runStateT app (AppState 0 flagINT_)

在这里,我已在

main
中安装了处理程序,我可以直接访问在那里创建的
flagInt_
可变引用。或者,您可以通过从状态读取标志引用来在应用程序 monad 中设置处理程序:

setINTHandler :: StateT AppState IO ()
setINTHandler = do
  flagINT_ <- gets flagINT
  liftIO $ installHandler sigINT (Catch (writeIORef flagINT_ True)) Nothing
  pure ()

有几点需要注意:

  • 尽管
    flagInt
    是可变
    State
    的字段,但这只是为了方便。
    flagInt
    仅作为只读字段访问,为实际可变标志提供可变引用。它也可以是单独的
    Reader
    的一部分。
  • 此处使用
    IORef
    和非原子
    writeIORef
    对于这个特定示例来说是足够线程安全的。如果您在处理程序中对状态执行更复杂的操作),那么您可能需要更加小心,也许改用
    MVar
© www.soinside.com 2019 - 2024. All rights reserved.