2015-09-10 12 views
5

私はハスケルのモナドシステムを理解しようとしています。私のこれまでのプログラミング経験の約80%はC言語ですが、皮肉なことにハスケルの不可欠な部分は最も理解しにくい部分です。リスト操作と遅延評価ははるかに明確でした。とにかく私はghcにこのコードを受け入れさせたいと思っています。コードが意味をなさないことはわかっています。最も明白に、私はBoolを渡しています。IO Boolが期待されます。しかしそれが唯一の問題ではありません。私はこれが愚かな質問だと知っていますが、ハスケル語の理解を深めてください。ハスケルの命令的ループ

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が必要ではないことを知っていますが、使用するのはとても面白かったです。ハスケルは本当に限界がなく、非常に強力です。

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 < - i + 1 'は2つの異なる' i'を参照します。 –

+1

ハスケルでは 'while'構造が使われることはほとんどありません。ハスケルでは、命令型言語に慣れている人にとって自然な方法で"変数 "を使用することは実際にはできません。あなたは同じことをもっと扱いにくいことができますが、 'Data.IORef'や' Control.Concurrent.MVar'のような可変参照を使わなければなりません。変更可能な更新が本当に必要な場合を除き、一般的に機能的に表現する方が良いです。 –

+1

このように 'IORef'を使用すると、ループカウンタが「boxed」になるので、新しい「Int」ボックスが各繰り返しで割り当てられ、カウンタへのアクセスにはポインタ間接を伴います。より機能的なスタイルのカウンターを扱うとき、GHCは通常それをアンボックスすることができ、より高速なコードにつながります。 – dfeuer

答えて

6

型チェックから、あなたのコードを維持二つのものがあります。

  1. はあなたのwhile機能がIO Boolを期待するが、あなたはそれをタイプBoolの表現であるi < 10を与えます。 BoolIO Boolにするには、単にreturnを使用します。

  2. i <- 0と書くと、リテラルゼロをモナドの値として使用しようとしますが、そうではありません。

    main = do 
        i <- 0 
        ... 
    

    は、この問題を解決するには、あなたもreturn経由0を促進することができ

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

に同等であることを覚えておいてください。

したがって、あなたは

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

で終わるしかし、これはまだあなたがするつもり何をしません。その理由は、i <- return (i + 1)の最初(左端)iiとは異なるであるということですi <- return 0にあります。 変数をシャドーイングして、同じ名前の新しい変数を作成してから印刷します。だから、実際にはカウンターを一切打つことはありません。

私は楽しいことを嫌っているわけではありませんが、本当に立ち往生した場合:whileM機能を含む便利なモナディックループ機能を公開するmonad-loopsパッケージがあります。

+0

OPのwhileは、そのパッケージの 'whileM_'(やや一般的なタイプですが、重要ではありません)とほぼ同等です。私はそれが問題のどこにあるのだろうとは思わない。 –

+0

['loops'](https://hackage.haskell.org/package/loops)や[' control-monad-loop'](https://hackage.haskellなど)のような他のライブラリがあります。 org/package/control-monad-loop)を提供しています。 'monad-loops'はいくつかのクールな並行性ツールを持っていますが、そうでなければそれほど一般的ではないようです。 – dfeuer

9

このアプローチの問題は、iが可変変数ではないことです。 IORefを使用することもできますが、より効果的なアプローチは、各繰り返しで現在の状態を渡すことです。

その後、
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

ローカル状態(ステートおよび関連モナド変換)で溶液を行うことができますグローバルとは反対に、:あなたは、現在の値を取るために、あなたのwhileM体と条件を書き換えることができ状態(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