我想写一个函数
forkos_try :: IO (Maybe α) -> IO (Maybe α)
采用命令x
。 x
是一个命令式操作,它首先改变状态,然后检查该状态是否混乱。 (它不执行任何外部操作,这需要某种操作系统级别的沙盒来恢复状态。)
x
评估为Just y
,则forkos_try
会返回Just y
。forkos_try
回滚状态,并返回Nothing
。在内部,它应该fork()
进入线程parent
和child
,x
上运行child
。
x
成功,child
应继续投放(返回x
的结果),parent
应该会死parent
应继续投放(返回Nothing
),child
应该 问题:使用与forkos_try
相同或更强大的语义编写内容的方法是什么?的 N.B。 - 状态变异(由x
)位于外部库中,无法在线程之间传递。因此,保持活动的线程的语义很重要。
正式地,“继续运行”意味着“执行一些延续rest :: Maybe α -> IO ()
”。但是,这种延续并没有在代码中保持明确。
对于我的情况,我认为(当时)会以不同的方式编写它,使用forkOS
(它将运行整个计算child
),因为我可以写一个rest
的显式表达式。但是,我无法弄清楚如何使用原始函数forkOS
让我感到麻烦 - 人们会认为它足以支持任何特定情况(可能看起来像是一个高级API,比如forkos_try
)。
编辑 - 如果问题仍然不明确[http://pastebin.com/nJ1NNdda],请查看带有明确rest
的示例代码。
P.S。我暂时没有编写并发代码;希望我对POSIX fork()
的了解是正确的!提前谢谢。
答案 0 :(得分:2)
如果你明确地建模状态,那么事情要简单得多。
someStateFunc :: (s -> Maybe (a, s))
-- inside some other function
case someStateFunc initialState of
Nothing -> ... -- it failed. stick with initial state
Just (a, newState) -> ... -- it suceeded. do something with
-- the result and new state
对于不可变状态,“回滚”很简单:继续使用initialState
。而“不回滚”也很简单:只需使用newState
。
所以......我从你的解释中假设这个“外部库”执行一些非常重要的IO效果,但这些效果仅限于一些可知和可逆的操作(修改文件,IORef等)。没有办法扭转一些事情(发射导弹,写入stdout等),所以我在这里看到两个选择之一:
当然,这两者实际上都是相同的方法:分叉世界。一个世界在运行,一个世界没有。如果行动成功,那么这个世界将继续存在;否则,另一个世界继续。您建议通过构建forkOS
来实现此目的,这将克隆程序的整个状态,但这不足以处理,例如,文件修改。请允许我建议一种更接近于不可变状态的简单性的方法:
tryIO :: IO s -> (s -> IO ()) -> IO (Maybe a) -> IO (Maybe a)
tryIO save restore action = do
initialState <- save
result <- action
case result of
Nothing -> restore initialState >> return Nothing
Just x -> return (Just x)
在这里,您必须提供一些数据结构s
,以及从所述数据结构到save
和restore
的方法。这使您可以灵活地执行任何您认为必要的克隆。 (例如save
可以将某个文件复制到临时位置,然后restore
可以将其复制回来并删除临时文件。或者save
可以复制某些IORef的值,然后restore
可以将价值放回去。)这种方法可能不是最有效的方法,但它非常简单。