Haskell中的命令循环

xiv*_*r77 8 monads 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
Run Code Online (Sandbox Code Playgroud)

这是我最终做到的.我知道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)
Run Code Online (Sandbox Code Playgroud)

Lee*_*Lee 10

这种方法的问题在于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
Run Code Online (Sandbox Code Playgroud)

那么你可以做到

whileM (< 10) (\i -> print i >> return (i + 1)) 0
Run Code Online (Sandbox Code Playgroud)


Fre*_*abe 6

有两件事可以使您的代码远离类型检查:

  1. 你的while函数需要一个,IO Bool但你给它的i < 10是一个类型的表达式Bool.要打开一个BoolIO Bool,简单地使用return.

  2. 当你写作时i <- 0,尝试使用文字零作为monadic值,但事实并非如此.记住这一点

    main = do
        i <- 0
        ...
    
    Run Code Online (Sandbox Code Playgroud)

    相当于

    main = 0 >>= \i -> do ...
    
    Run Code Online (Sandbox Code Playgroud)

要解决此问题,您还可以推广0通道return.

因此,你最终得到了

main :: IO ()
main = do
    i <- return 0
    while (return (i < 10)) $ do
        i <- return (i + 1)
        print i
Run Code Online (Sandbox Code Playgroud)

然而,这仍然不会做你打算做什么:原因是第一个(最左边)ii <- return (i + 1)不同ii <- return 0.您正在隐藏变量,创建一个具有相同名称的新变量,然后打印该变量.所以你根本没有碰到任何反击.

我不想破坏这种乐趣,但如果你真的卡住了:有一个monad-loops包暴露了一些有用的monadic循环函数,包括一个whileM函数.


n. *_* m. 5

与全局状态(IORef和朋友)相对的具有本地状态(状态和相关联的 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)
Run Code Online (Sandbox Code Playgroud)

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