【发布时间】:2015-09-10 14:15:03
【问题描述】:
我正在尝试了解 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)
【问题讨论】:
-
让我们从显而易见的开始。
i不是一个可变变量,并且不会仅仅因为你有一个 monad 而成为一个变量。i <- i + 1指的是两个不同的is。 -
while构造在 Haskell 中很少使用,我认为正是因为在 Haskell 中,它实际上并不允许您以自然方式使用命令式语言的人使用“变量”。您可以更尴尬地做同样的事情,但您必须使用可变引用,例如Data.IORef或Control.Concurrent.MVar中的引用。除非您确实需要可变更新,否则通常最好用函数式表达它。 -
请注意,以这种方式使用
IORef会导致循环计数器被“装箱”,因此每次迭代都会分配一个新的Int框,并且访问计数器涉及指针间接。当您处理更多功能风格的计数器时,GHC 通常可以将其拆箱,从而获得更快的代码。 -
另外,你为什么要使用
Foreign的所有东西?你真的需要处理外国代码吗?你有理由让垃圾收集器把数组当作外来的吗?如果没有,您应该考虑使用未装箱的可变向量(来自vector包)。 -
还有一些库提供未装箱的可变变量;我不知道他们的表现如何。基本思想是使用可变字节数组来存储“变量”。