使用"除非"进行monadic递归的抽象

The*_*ive 1 monads haskell

如果可以为以下情况编写抽象,我正在尝试解决问题.假设我有一个a带有函数的类型,a -> m Bool例如MVar BoolreadMVar.为了抽象出这个概念,我为类型及其函数创建了一个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以便它使用MPredicatedoUnless

忽略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但执行相当不同的异步任务.在每一个中,我都编写了一个执行适当循环的自定义函数.

我正在努力学习"不要写大型程序"的方法.我想要做的是将代码块重构到他们自己的迷你库中,这样我就不会构建一个大型程序,而是组装许多小程序.但到目前为止,这种特殊的抽象正在逃避我.

我非常感谢您对我如何做到这一点的任何想法!

dan*_*iaz 6

您希望干净地结合具有副作用,延迟和独立停止条件的有状态动作.

在这些情况下,包中的迭代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功能可以被理解为三件事的"总和":

有了这个,我们可以写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.当然,这可能会导致非终止!