Haskell “一元递归”的抽象;除非;

Haskell “一元递归”的抽象;除非;,haskell,monads,Haskell,Monads,我正试图找出是否有可能为以下情况编写一个抽象。假设我有一个带有函数a->m Bool的类型a,例如MVar Bool和readMVar。为了抽象出这个概念,我为类型及其函数创建了一个新的类型包装器: newtype MPredicate m a = MPredicate (a,a -> m Bool) 我可以定义一个相当简单的操作,如下所示: doUnless :: (Monad m) => Predicate m a -> m () -> m () doUnless

我正试图找出是否有可能为以下情况编写一个抽象。假设我有一个带有函数
a->m Bool
的类型
a
,例如
MVar Bool
readMVar
。为了抽象出这个概念,我为类型及其函数创建了一个新的类型包装器:

newtype MPredicate m a = MPredicate (a,a -> m Bool)
我可以定义一个相当简单的操作,如下所示:

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")
是否可以重构
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
旁白:我觉得
fix
可以用来使上面的内容更整洁,尽管我仍然很难弄清楚如何使用它

但是
cycleeunless
foobar
上不起作用,因为
foobar'
的类型实际上是
Int->IO()
(使用
print x'

我还想进一步研究这个抽象,这样它就可以围绕Monad进行线程处理。有状态的单子就更难了。例如

-- 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'
--编辑:更新了以下内容,以显示如何使用代码的示例
{-^^具有MVar^^-}的某个父函数
循环测试::(对于所有s.ST s(STArray s Int))->IO()
cycleST sta=readMVar mvb>=\b->除非b$do

n我不确定你的
MPredicate
在做什么。 首先,与其新键入元组,不如使用普通的algebric数据类型

data MPredicate a m=MPredicate a(a->m Bool)

其次,您使用它的方式,
MPredicate
相当于
m Bool
。 Haskell是懒散的,因此不需要传递函数及其参数(即使 它对严格的语言很有用)。只需传递结果,就可以在需要时调用该函数

我的意思是,与其四处传递
(x,f)
,不如传递
fx
当然,如果您不想延迟计算,并且在某个时候确实需要参数或函数以及结果,则可以使用元组

无论如何,如果您的
MPredicate
仅用于延迟功能评估,
MPredicat
减少到
m Bool
doUnless
,除非

您的第一个示例完全等效:

main = do
   b <- newMVar False
   unless (readMVar b) (print "foo")
main=do

b我不确定你的
MPredicate
在做什么。 首先,与其新键入元组,不如使用普通的algebric数据类型

data MPredicate a m=MPredicate a(a->m Bool)

其次,您使用它的方式,
MPredicate
相当于
m Bool
。 Haskell是懒散的,因此不需要传递函数及其参数(即使 它对严格的语言很有用)。只需传递结果,就可以在需要时调用该函数

我的意思是,与其四处传递
(x,f)
,不如传递
fx
当然,如果您不想延迟计算,并且在某个时候确实需要参数或函数以及结果,则可以使用元组

无论如何,如果您的
MPredicate
仅用于延迟功能评估,
MPredicat
减少到
m Bool
doUnless
,除非

您的第一个示例完全等效:

main = do
   b <- newMVar False
   unless (readMVar b) (print "foo")
main=do

b
MPredicate
在这里是相当多余的
m Bool
可以替代使用。该软件包包含大量具有
m Bool
条件的控制结构
whileM_uu
在这里特别适用,尽管我们需要为我们正在处理的
Int
包含一个
State
monad:

import Control.Monad.State
import Control.Monad.Loops
import Control.Applicative

foobar :: MVar Bool -> IO ()
foobar mvb = (`evalStateT` (0 :: Int)) $ 
  whileM_ (not <$> lift (readMVar mvb)) $ do
    modify (+1) 
    lift . print =<< get    
    lift $ threadDelay 1000000  
它在一元设置中更方便、更模块化,因为我们总是可以从纯
Bool
m Bool
,但反之亦然

foobar :: MVar Bool -> IO ()
foobar mvb = go 0
    where
        go :: Int -> IO ()
        go x = unlessM (readMVar mvb) $ do 
            let x' = x + 1
            print x'
            threadDelay 1000000
            go x' 
您提到了
fix
;有时人们确实会将其用于特殊的一元循环,例如:

printUntil0 :: IO ()
printUntil0 = 
  putStrLn "hello"

  fix $ \loop -> do
    n <- fmap read getLine :: IO Int
    print n
    when (n /= 0) loop

  putStrLn "bye"

MPredicate
在这里是多余的
m Bool
可以替代使用。该软件包包含大量具有
m Bool
条件的控制结构
whileM_uu
在这里特别适用,尽管我们需要为我们正在处理的
Int
包含一个
State
monad:

import Control.Monad.State
import Control.Monad.Loops
import Control.Applicative

foobar :: MVar Bool -> IO ()
foobar mvb = (`evalStateT` (0 :: Int)) $ 
  whileM_ (not <$> lift (readMVar mvb)) $ do
    modify (+1) 
    lift . print =<< get    
    lift $ threadDelay 1000000  
它在一元设置中更方便、更模块化,因为我们总是可以从纯
Bool
m Bool
,但反之亦然

foobar :: MVar Bool -> IO ()
foobar mvb = go 0
    where
        go :: Int -> IO ()
        go x = unlessM (readMVar mvb) $ do 
            let x' = x + 1
            print x'
            threadDelay 1000000
            go x' 
您提到了
fix
;有时人们确实会将其用于特殊的一元循环,例如:

printUntil0 :: IO ()
printUntil0 = 
  putStrLn "hello"

  fix $ \loop -> do
    n <- fmap read getLine :: IO Int
    print n
    when (n /= 0) loop

  putStrLn "bye"

您希望将具有副作用、延迟和独立停止条件的有状态操作干净地结合起来

在这些情况下,
免费
软件包中的

此monad transformer允许您将(可能是非结束的)计算描述为一系列离散步骤。更好的是,它允许您使用
mplus
交错“阶梯式”计算。当任何单个计算停止时,组合计算停止

一些初步进口:

import Data.Bool
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Iter (delay,untilJust,IterT,retract,cutoff)
import Control.Concurrent
您的
foobar
函数可以理解为三件事的“总和”:

  • 一种计算,在每一步只从
    MVar
    读取数据,当
    MVar
    True
    时完成

    untilTrue :: (MonadIO m) => MVar Bool -> IterT m ()  
    untilTrue = untilJust . liftM guard . liftIO . readMVar
    
  • 每一步都有延迟的无限计算

    delays :: (MonadIO m) => Int -> IterT m a
    delays = forever . delay . liftIO . threadDelay
    
  • 打印一系列递增数字的无限计算

    foobar' :: (MonadIO m) => Int -> IterT m a 
    foobar' x = do
        let x' = x + 1
        liftIO (print x')
        delay (foobar' x')
    
有了它,我们可以将
foobar
写成:

foobar :: (MonadIO m) => MVar Bool -> m ()
foobar v =  retract (delays 1000000 `mplus` untilTrue v `mplus` foobar' 0)
最妙的是,您可以很容易地更改或删除“停止条件”和延迟

一些澄清:

  • delay
    函数不是IO中的延迟,它只是