Haskell 中的命令式循环

imperative loop in Haskell

我正在尝试了解 Haskell 中的 monad 系统。我之前大约 80% 的编程经验是用 C 语言编写的,但具有讽刺意味的是 Haskell 的命令式部分是最难理解的。列表操作和懒惰评估更加清晰。不管怎样,我想让 ghc 接受这段代码。我知道代码根本没有意义。最明显的是,我传递了一个 Bool ,其中 IO Bool 是预期的。但这不是唯一的问题。我知道这是一个愚蠢的问题,但请帮助我加深对 Haskell 语言的理解。

import Control.Monad

while :: Monad m => m Bool -> m () -> m ()
while cond action = do
  c <- cond
  when c $ do
    action
    while cond action

main :: IO ()
main = do
  i <- 0
  while (i < 10) $ do
    i <- i + 1
    print i

这是我最终做到的。我知道 allocaArray 不是必需的,但使用起来非常有趣。 Haskell真的没有极限,很厉害

import Control.Monad
import Data.IORef
import Foreign.Ptr
import Foreign.Storable
import Foreign.Marshal.Array

while :: Monad m => m Bool -> m () -> m ()
while cond action = do
  c <- cond
  if c then do
    action
    while cond action
  else return ()

main :: IO ()
main = do
  let n = 10
  allocaArray n $ \p -> do
    i <- newIORef 0
    while (liftM (< n) (readIORef i)) $ do
      i2 <- readIORef i
      poke (advancePtr p i2) i2
      modifyIORef i (+ 1)
    writeIORef i 0
    while (liftM (< n) (readIORef i)) $ do
      i2 <- readIORef i
      (peek $ advancePtr p i2) >>= print
      modifyIORef i (+ 1)

有两件事使您的代码无法进行类型检查:

  1. 你的 while 函数需要一个 IO Bool 但你给它 i < 10 这是一个 Bool 类型的表达式。要将 Bool 转换为 IO Bool,只需使用 return.

  2. 当您编写 i <- 0 时,您尝试使用文字零作为一元值,但事实并非如此。请记住

    main = do
        i <- 0
        ...
    

    等同于

    main = 0 >>= \i -> do ...
    

要解决此问题,您还可以通过 return.

推广 0

因此,你最终得到

main :: IO ()
main = do
    i <- return 0
    while (return (i < 10)) $ do
        i <- return (i + 1)
        print i

但是,这仍然无法实现您的预​​期:原因是 i <- return (i + 1) 中的第一个(最左边)i 不同 而不是 i <- return 0 中的 i。您正在 隐藏 变量,创建一个同名的新变量,然后打印它。所以你实际上根本没有碰到任何计数器。

我不想破坏乐趣,但如果您真的遇到困难:有一个 monad-loops package which exposes a couple of useful monadic loop functions, including a whileM 函数。

这种方法的问题是 i 不是可变变量。您可以使用 IORef,但更实用的方法是通过每次迭代传递当前状态。您可以重写 whileM 正文和条件以采用当前值:

whileM :: Monad m => (a -> Bool) -> (a -> m a) -> a -> m ()
whileM test act init =
   when (test init) $ (act init) >>= whileM test act

那你就可以了

whileM (< 10) (\i -> print i >> return (i + 1)) 0

与全局状态(IORef 和朋友)相对的本地状态(State 和关联的 monad 转换器)的解决方案:

import Control.Monad
import Control.Monad.State

while :: Monad m => m Bool -> m () -> m ()
while cond action = do
  c <- cond
  when c $ do
    action
    while cond action

main :: IO ()
main = do 
  runStateT (while cond body) 1 
  return ()

body :: StateT Integer IO ()
body = do
    x <- get
    liftIO $ print x
    put (x + 1)
    return ()

cond :: StateT Integer IO Bool
cond = do
    x <- get
    return (x < 10)

循环体和循环条件是明确的,并且为了清楚起见而命名;可以写例如while (liftM (< 10) get) body.