我正在尝试理解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)
有两件事可以使您的代码远离类型检查:
你的while函数需要一个,IO Bool但你给它的i < 10是一个类型的表达式Bool.要打开一个Bool到IO Bool,简单地使用return.
当你写作时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)
然而,这仍然不会做你打算做什么:原因是第一个(最左边)i中i <- return (i + 1)是不同比i的i <- return 0.您正在隐藏变量,创建一个具有相同名称的新变量,然后打印该变量.所以你根本没有碰到任何反击.
我不想破坏这种乐趣,但如果你真的卡住了:有一个monad-loops包暴露了一些有用的monadic循环函数,包括一个whileM函数.
与全局状态(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.