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)
有两件事使您的代码无法进行类型检查:
你的 while
函数需要一个 IO Bool
但你给它 i < 10
这是一个 Bool
类型的表达式。要将 Bool
转换为 IO Bool
,只需使用 return
.
当您编写 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
.
我正在尝试了解 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)
有两件事使您的代码无法进行类型检查:
你的
while
函数需要一个IO Bool
但你给它i < 10
这是一个Bool
类型的表达式。要将Bool
转换为IO Bool
,只需使用return
.当您编写
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
.