2015-09-10 107 views
5

我想了解Haskell中的monad系統。我以前編程實驗的大約80%是用C語言編寫的,但具有諷刺意味的是,Haskell的必要部分是最難理解的。列表操作和懶惰評估更清晰。無論如何,我想讓ghc接受這段代碼。我知道代碼根本沒有意義。最明顯的是,我通過了Bool,其中IO Bool預計。但這不是唯一的問題。我知道這是一個愚蠢的問題,但請幫助我進一步理解Haskell語言。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) 
+2

讓我們從明顯的開始。 '我'不是一個可變的變量,並不僅僅因爲你有一個單子。 'i < - i + 1'是指兩個不同的'i's。 –

+1

Haskell中'while'構造很少使用,我想這正是因爲在Haskell中它實際上並不允許您以習慣於使用命令式語言的人的自然方式使用「變量」。你*可以*更笨拙地做同樣的事情,但是你必須使用可變引用,如'Data.IORef'或'Control.Concurrent.MVar'中的可變引用。除非你真的需要可變的更新,否則通常更好地表達它的功能。 –

+1

請注意,以這種方式使用'IORef'會導致循環計數器被「裝盒」,因此每次迭代都會分配一個新的'Int'盒,並且訪問計數器涉及指針間接。當你處理更加功能強大的計數器時,GHC通常可以將它解開,從而加快編碼速度。 – dfeuer

回答

6

有哪些讓你的代碼的類型檢查兩件事情:

  1. while函數需要一個IO Bool但你給它i < 10這是Bool類型的表達式。要將Bool轉換爲IO Bool,只需使用return即可。

  2. 當您編寫i <- 0時,您嘗試將文字零作爲單值使用,而不是。請記住,

    main = do 
        i <- 0 
        ... 
    

    相當於

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

爲了解決這個問題,你也可以通過促進的return0

因此,你最終

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

然而,這仍然不會做你打算做什麼:原因是在i <- return (i + 1)第一(最左邊)i不同ii <- return 0。你是shadowing這個變量,用相同的名字創建一個新的變量,然後你打印它。所以你根本不會碰到任何計數器。

我不想破壞的樂趣,但如果你真的卡住:有一個monad-loops包,它暴露了一些有用的一元環的功能,包括whileM功能。

+0

OP中的'while'與該包的'whileM_'(略少於一般類型,但並不重要)幾乎等價。我不認爲這是問題所在。 –

+0

還有一些其他的loopy庫,例如['loops'](https://hackage.haskell.org/package/loops)和['control-monad-loop'](https://hackage.haskell。 org/package/control-monad-loop),它提供了更好地符合OP需求的工具。 'monad-loops'有一些很酷的併發工具,但是它看起來不那麼普遍。 – dfeuer

9

這種方法的問題是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 
3

與當地州(國家和相關的單子轉換)溶液,而不是全球狀態(IORef和朋友):

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