我想我的问题可以归结为是/否问题:“
IO
单子只能通过I/O操作保持状态吗?”换句话说,如果我必须编写一个操作,我的理解是否正确
run :: IO String
由其他一些
IO
monad 实例重复执行,我无法编写它来保留状态的内存,从调用到下一个状态,而不是通过在某处序列化状态(例如,到文件或在调用者中(如果它提供 API 来执行此操作)?
但是如果答案是“是的,你不能这样做”,那么问题就是标题中的问题:如何为 XMobar 编写一个保持本地状态的监视器?
所有监视器都通过
Run $ SomeMonitor
运行,其中SomeMonitor
必须是Runnable
类型,即它可以是实现(Exec r, Read r, Show r)
接口的任何类型,其中Exec
是有趣的,因为它是你的地方放置插件的业务逻辑,因为它具有类型IO String
,因此它可以使用String
创建XMobar显示的IO
。
这是我的第一个非常简单的实验,
data ArchUpdates = ArchUpdates deriving (Read, Show)
instance Exec ArchUpdates where
rate _ = 36000
run _ = fmap (makeMessage . length . lines) $ getCommandOutput "checkupdates"
where
makeMessage :: Int -> String
makeMessage = show -- simplified version
我可以通过 ArchLinux 系统上的
pacman
确定可以进行多少次更新。
现在,显然 XMobar 显示的值会随着时间的推移而变化,但不是,因为它对时间有明确的依赖性;这是因为系统的状态发生了变化,并且变化是通过 I/O 操作检索的,在上面的代码中用
getCommandOutput "checkupdates"
表示。
但是如果我想要一个明确依赖时间的插件怎么办?我的意思是,一个每秒更新一次并依次显示多个字符串之一的插件?假设它先显示
"A long time ago"
,然后显示 "in a galaxy far,"
,最后显示 "far away..."
,然后再次循环。
我的要求让我想到
State
,但是Exec
约束迫使我使用IO
单子,所以我不认为StateT
变压器是可行的方法,因为我可以不要在 IO
中使用 anothermonad 包装
run
。我宁愿需要 IO
来包装一些状态...但我无法更改 IO
!
因此,我认为保持状态的唯一方法是将其序列化到某处,然后重新读取它:
instance Exec MyPlugin where
rate _ = 1000
run _ = do
old <- getCurrentStateOfSelf
return $ makeNewState old
其中
makeNewState
是String -> String
1,但是getCurrentStateOfSelf
应该由XMobar的API提供,否则还剩下什么?将状态写入文件?
run _ = do
old <- readStateFromFile
let new = makeNewState old
writeStateToFile new
return new
这太可怕了。
我尝试询问 ChatGPT,它给了我这个:
import Control.Monad.State
func :: StateT Int IO String
func = do
currentState <- get
liftIO $ putStrLn $ "Current state: " ++ show currentState
modify (+1)
return "Hello, World!"
main :: IO ()
main = do
(result, newState) <- runStateT func 0
putStrLn $ "Result: " ++ result
putStrLn $ "Final state: " ++ show newState
但我不认为这是一种解决方案,因为
main
仍然负责对 runStateT
进行多次调用,将状态从一个调用传输到另一个调用;这不像多次调用 to main
正在访问 func
的连续状态,这正是我想要的。
我想答案可能是我碰壁了:XMobar 监视器是在
IO
monad 中设计的,而我需要 State
monad(那么显然 XMobar 仍然会使用 IO
monad 来显示屏幕上的东西,因为毕竟是一个程序,所以它来自于编译一个main
函数,也就是IO ()
)。
(1) 对于上面 3 个不同的字符串,它可能是一个带有
[String]
本地 state
的闭包,它接受 String
,将其定位在 cycle state
中,然后返回该项目.
这有点取决于。您向 xmobar 提供这些 IO 操作的 API 是什么?如果您可以执行自己的 IO 来创建 IO 操作,则可以构造一个位于 IO 操作本身之外的 IORef,以便它引用和存储数据。但是,如果您无法与 xmobar 的启动方式进行交互,它要求你做的只是一些
IO a
操作,你不能真正将外部 IORef 偷偷带进去(除非你用 unsafePerformIO
作弊。
但是让我们想象一下您自己正在运行 xmobar,并且它返回某种 IO 操作:
runXmobar :: [IO String] -> IO ()
runXmobar = undefined -- implemented by xmobar
您的
main
可能看起来像这样:
main = do
ref <- newIORef 0
runXmobar [uptick ref]
uptick :: IORef Int -> IO String
uptick r = print =<< atomicModifyIORef' next r
where next x = (x + 1, show x)