如果可以为以下情况编写抽象,我正在尝试解决问题.假设我有一个a带有函数的类型,a -> m Bool例如MVar Bool和readMVar.为了抽象出这个概念,我为类型及其函数创建了一个newtype包装器:
newtype MPredicate m a = MPredicate (a,a -> m Bool)
Run Code Online (Sandbox Code Playgroud)
我可以像这样定义一个相当简单的操作:
doUnless :: (Monad m) => Predicate m a -> m () -> m ()
doUnless (MPredicate (a,mg)) g = mg a >>= \b -> unless b g
main = do
b <- newMVar False
let mpred = MPredicate (b,readMVar)
doUnless mpred (print "foo")
Run Code Online (Sandbox Code Playgroud)
在这种情况下doUnless会打印"foo".旁白:我不确定类型类是否更适合使用而不是newtype.
现在取下面的代码,输出一个递增的数字,然后等待一秒钟并重复.这样做直到它通过MVar收到"关闭"指令.
foobar :: MVar Bool -> IO ()
foobar mvb = foobar' 0
where
foobar' :: Int -> IO ()
foobar' x = readMVar mvb >>= \b -> unless b $ do
let x' = x + 1
print x'
threadDelay 1000000
foobar' x'
goTillEnter :: MVar Bool -> IO ()
goTillEnter mv = do
_ <- getLine
_ <- takeMVar mv
putMVar mv True
main = do
mvb <- newMVar False
forkIO $ foobar mvb
goTillEnter mvb
Run Code Online (Sandbox Code Playgroud)
有可能重构,foobar以便它使用MPredicate和doUnless?
忽略foobar'我的实际实现我可以想到一种简单的方法来做类似的事情:
cycleUnless :: x -> (x -> x) -> MPredicate m a -> m ()
cycleUnless x g mp = let g' x' = doUnless mp (g' $ g x')
in g' $ g x
Run Code Online (Sandbox Code Playgroud)
旁白:我觉得fix可以用来制作上面的整理器,但我仍然无法解决如何使用它
但是cycleUnless不会起作用,foobar因为其foobar'实际类型Int -> IO ()(来自使用print x').
我还想进一步采用这种抽象,以便它可以在Monad上进行线程化.有状态的Monads变得更加困难.例如
-- EDIT: Updated the below to show an example of how the code is used
{- ^^ some parent function which has the MVar ^^ -}
cycleST :: (forall s. ST s (STArray s Int Int)) -> IO ()
cycleST sta = readMVar mvb >>= \b -> unless b $ do
n <- readMVar someMVar
i <- readMVar someOtherMVar
let sta' = do
arr <- sta
x <- readArray arr n
writeArray arr n (x + i)
return arr
y = runSTArray sta'
print y
cycleST sta'
Run Code Online (Sandbox Code Playgroud)
我有类似于上面使用RankNTypes的东西.现在还有一个额外的问题,即试图穿过存在主义s,这不太可能通过类似的抽象来进行类型检查cycleUnless.
此外,这简化了以使问题更容易回答.我还使用了一组信号量,这些信号量MVar [MVar ()]类似于MVar模块中的跳过通道示例.如果我能解决上述问题,我也计划推广信号量.
最终这不是一些阻塞问题.我有3个应用程序组件在一个循环中运行,MVar Bool但执行相当不同的异步任务.在每一个中,我都编写了一个执行适当循环的自定义函数.
我正在努力学习"不要写大型程序"的方法.我想要做的是将代码块重构到他们自己的迷你库中,这样我就不会构建一个大型程序,而是组装许多小程序.但到目前为止,这种特殊的抽象正在逃避我.
我非常感谢您对我如何做到这一点的任何想法!
您希望干净地结合具有副作用,延迟和独立停止条件的有状态动作.
在这些情况下,包中的迭代monad变换器free可能很有用.
这个monad变换器允许您将(可能是无限的)计算描述为一系列离散步骤.更好的是,它让你用"交错"计算交错mplus.当任何单个计算停止时,组合计算停止.
一些初步进口:
import Data.Bool
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Iter (delay,untilJust,IterT,retract,cutoff)
import Control.Concurrent
Run Code Online (Sandbox Code Playgroud)
你的foobar功能可以被理解为三件事的"总和":
一计算,什么也不做,但是从阅读MVar每一步,并完成时Mvar为True.
untilTrue :: (MonadIO m) => MVar Bool -> IterT m ()
untilTrue = untilJust . liftM guard . liftIO . readMVar
Run Code Online (Sandbox Code Playgroud)无限计算,每步都需要延迟.
delays :: (MonadIO m) => Int -> IterT m a
delays = forever . delay . liftIO . threadDelay
Run Code Online (Sandbox Code Playgroud)无限计算,打印一系列不断增加的数字.
foobar' :: (MonadIO m) => Int -> IterT m a
foobar' x = do
let x' = x + 1
liftIO (print x')
delay (foobar' x')
Run Code Online (Sandbox Code Playgroud)有了这个,我们可以写foobar为:
foobar :: (MonadIO m) => MVar Bool -> m ()
foobar v = retract (delays 1000000 `mplus` untilTrue v `mplus` foobar' 0)
Run Code Online (Sandbox Code Playgroud)
关于这一点的好处是你可以很容易地改变或消除"停止条件"和延迟.
一些澄清:
该delay函数不是IO的延迟,它只是告诉迭代monad变换器"将参数放在一个单独的步骤中".
retract从迭代monad变换器带回到基础monad.这就像说"我不关心步骤,只是运行计算".如果要限制最大迭代次数,可以组合retract使用cutoff.
untilJust通过在每个步骤中重试m (Maybe a)将基本monad 的值转换为a IterT m a,直到Just返回a.当然,这可能会导致非终止!