2012-01-28 8 views
2

http://www.haskell.org/haskellwiki/State_Monadの具体例は、実際のコードをどのようにモナドで書くかを理解するのに非常に役立ちます(stackoverflow/9014218も参照してください)。しかし、私たちのほとんどはOOバックグラウンドから来ているので、OOプログラムをhaskellにマッピングすると、同等のhaskellコードを書く方法を示すのに役立ちます。 (はい、2つのパラダイムは全く異なっており、OOスタイルのコードを直接haskellに変換するのは賢明ではありませんが、チュートリアルとして一度だけです。)OOスタイルのコードを模倣するためのhaskellモナシ

ここではOOスタイルのコードを使用して、オブジェクトのインスタンスを作成し、それぞれのメンバ変数を変更するメンバ関数を呼び出し、最後にそれらを出力します。私たちはこれをhaskell stateのモナドを使ってどのように書きますか?

class A: 
    int p; 
    bool q; 
    A() { p=0; q=False;} // constructor 
    int y() { // member function 
     if(q) p++; else p--; 
     return p; 
    } 
    bool z() { // member function 
     q = not q; 
     return q; 
    } 
main: 
    // main body - creates instances and calls member funcs 
    a1 = A; a2 = A; // 2 separate instances of A 
    int m = a1.y(); 
    m = m + a1.y(); 
    bool n = a2.z(); 
    print m, n, a1.p, a1.q, a2.p, a2.q; 
+3

私はOOが本当に状態のモナドとはあまり関係ないと思います。状態モナドは状態(思考変数)のためのものです。 OOはカプセル化と多態性のためのものです(モジュールとタイプクラスを考えてください) – hugomg

+1

@Rog:http://homepages.cwi.nl/~ralf/OOHaskell/paper.pdfを見ましたか? – Landei

+0

ありがとうございます。優れた用紙。 – R71

答えて

5

ダイレクト翻訳はのようになります。

module Example where 

import Control.Monad.State 

data A = A { p :: Int, q :: Bool } 

-- constructor 
newA :: A 
newA = A 0 False 

-- member function 
y :: State A Int 
y = do 
    b <- getQ 
    modifyP $ if b then (+1) else (subtract 1) 
    getP 

-- member function 
z :: State A Bool 
z = do 
    b <- gets q 
    modifyQ not 
    getQ 

main :: IO() 
main = do 
    let (m,a1) = flip runState newA $ do 
     m <- y 
     m <- (m +) `fmap` y 
     return m 
    let (n,a2) = flip runState newA $ do 
     n <- z 
     return n 
    print (m, n, p a1, q a1, p a2, q a2) 

-- general purpose getters and setters 
getP :: State A Int 
getP = gets p 

getQ :: State A Bool 
getQ = gets q 

putP :: Int -> State A() 
putP = modifyP . const 

putQ :: Bool -> State A() 
putQ = modifyQ . const 

modifyP :: (Int -> Int) -> State A() 
modifyP f = modify $ \a -> a { p = f (p a) } 

modifyQ :: (Bool -> Bool) -> State A() 
modifyQ f = modify $ \a -> a { q = f (q a) } 

そして私はおそらく手動ゲッター/セッターを気にしてばかりlensesを使用することはありません。

{-# LANGUAGE TemplateHaskell, FlexibleContexts #-} 
module Main where 

import Control.Applicative 
import Control.Monad.State 
import Data.Lenses 
import Data.Lenses.Template 

data A = A { p_ :: Int, q_ :: Bool } deriving Show 
$(deriveLenses ''A) 

-- constructor 
newA :: A 
newA = A 0 False 

-- member function 
y :: MonadState A m => m Int 
y = do 
    b <- q get 
    if b then p $ modify (+1) else p $ modify (subtract 1) 
    p get 

-- member function 
z :: MonadState A m => m Bool 
z = do 
    q $ modify not 
    q get 


data Main = Main { a1_ :: A, a2_ :: A, m_ :: Int, n_ :: Bool } deriving Show 
$(deriveLenses ''Main) 

main :: IO() 
main = do 
    -- main body - creates instances and calls member funcs 
    print $ flip execState (Main undefined undefined undefined undefined) $ do 
    a1 $ put newA ; a2 $ put newA -- 2 separate instances of A 
    m . put =<< a1 y 
    m . put =<< (+) <$> m get <*> a1 y 
    n . put =<< a2 z 

は、しかし、それは、私がしようとOO-スタイルを模倣するために、後方の上にHaskellの曲げてるので、私は本当に、書きたいものではないのです。だからちょっと厄介だ。

私にとって、オブジェクト指向コードの本当の目的は、インターフェイスにプログラムすることです。私がこれらの種類のオブジェクトを使用しているとき、私はこれらの種類のメソッドをサポートするためにそれらに頼ることができます。だから、Haskellで、私は型クラスを使用してそれを行うだろう:

{-# LANGUAGE TemplateHaskell, FlexibleContexts #-} 
module Main where 

import Prelude hiding (lookup) 
import Control.Applicative 
import Control.Monad.State 
import Data.Lenses 
import Data.Lenses.Template 
import Data.Map 

class Show a => Example a where 
    -- constructor 
    new :: a 
    -- member function 
    y :: MonadState a m => m Int 
    -- member function 
    z :: MonadState a m => m Bool 

data A = A { p_ :: Int, q_ :: Bool } deriving Show 
$(deriveLenses ''A) 

instance Example A where 
    new = A 0 False 
    y = do 
    b <- q get 
    if b then p $ modify (+1) else p $ modify (subtract 1) 
    p get 
    z = do 
    q $ modify not 
    q get 

data B = B { v_ :: Int, step :: Map Int Int } deriving Show 
$(deriveLenses ''B) 

instance Example B where 
    new = B 10 . fromList $ zip [10,9..1] [9,8..0] 
    y = v get 
    z = do 
    i <- v get 
    mi <- lookup i `liftM` gets step 
    case mi of 
     Nothing -> return False 
     Just i' -> do 
     v $ put i' 
     return True 

data Main a = Main { a1_ :: a, a2_ :: a, m_ :: Int, n_ :: Bool } deriving Show 
start :: Example a => Main a 
start = Main undefined undefined undefined undefined 
$(deriveLenses ''Main) 

run :: Example a => State (Main a)() 
run = do 
    -- main body - creates instances and calls member funcs 
    a1 $ put new ; a2 $ put new -- 2 separate instances of a 
    m . put =<< a1 y 
    m . put =<< (+) <$> m get <*> a1 y 
    n . put =<< a2 z 

main :: IO() 
main = do 
    print $ flip execState (start :: Main A) run 
    print $ flip execState (start :: Main B) run 

をだから今、私はさまざまな種類ABに同じrunを再利用することができます。

+0

ここでは、私が答えで簡単に取り上げた非常に重要な点を強化します: 'State'モナドは同時に一つの状態しか追跡できないので、まず' a1'、次に 'a2'で何かしたい場合は、もう一度 'a1'を実行すると、コードははるかに扱いにくくなります。このコードは、2つの州が分離されているため、現時点ではいくらか読めるものです。 – dflemstr

+0

また、[lens](http://hackage.haskell.org/package/data-lens)と[accessor lenses](http://hackage.haskell。)を使用して、 'getFoo'と' putFoo'関数を実装できます。 org/package/data-lens-fd)を参照してください。次に、 'p〜= 2'(または何でも)を書くことができます。ここで' p'はレンズです。 – dflemstr

+0

@dflemstr:私は実際にレンズの部分で実際に遊んでいました。私は最初にその方法を行っていませんでした。私はそれをより高いレベルのコンセプトと考えています。この男は初心者のようでした。 – rampion

4

Stateモナドを使用してクラスをエミュレートすることはできません。これは、実行しているコードに「固執する」状態をモデル化するために使用され、「独立」でありオブジェクト指向のクラスに常駐する状態ではありません。

メソッドのオーバーライドと継承を望まない場合、HaskellのOOPクラスに最も近いのは、関連する関数でレコードを使用することです。その場合、すべての「クラスメソッド」が新しい「オブジェクト」を返すだけで、古い「オブジェクト」は変更されないことに注意する必要があります。例えば

data A = 
    A 
    { p :: Int 
    , q :: Bool 
    } 
    deriving (Show) 

-- Your "A" constructor 
newA :: A 
newA = A { p = 0, q = False } 

-- Your "y" method 
y :: A -> (Int, A) 
y a = 
    let newP = if q a then p a + 1 else p a - 1 
     newA = a { p = newP } 
    in (newP, newA) 

-- Your "z" method 
z :: A -> Bool 
z = not . q 

-- Your "main" procedure 
main :: IO() 
main = 
    print (m', n, p a1'', q a1'', p a2, q a2) 
    where 
    a1 = newA 
    a2 = newA 
    (m, a1') = y a1 
    (temp, a1'') = y a1' 
    m' = m + temp 
    n = z a2 

このプログラムを印刷:我々はma1の新しいバージョンを保存するために新しい変数を作成する必要がありました

(-3,True,-2,False,0,False) 

注意(私は最後に'を追加しました毎回)。 Haskellには言語レベルの可変変数がないので、その言語を使用しようとすべきではありません。

で、IO参照を使用して変数を変更することができます。

ただし、次のコードは、ハスケラー間で非常に不適切なコーディングスタイルとみなされます。私が教師で、このようなコードを書いた生徒がいたら、私はその課題に合格していません。このようなコードを書いたHaskellのプログラマーを採用した場合、このようなコードを書いた理由があまりないと、彼を解雇することを検討したいと思います。

import Data.IORef -- IO References 

data A = 
    A 
    { p :: IORef Int 
    , q :: IORef Bool 
    } 

newA :: IO A 
newA = do 
    p' <- newIORef 0 
    q' <- newIORef False 
    return $ A p' q' 

y :: A -> IO Int 
y a = do 
    q' <- readIORef $ q a 
    if q' 
    then modifyIORef (p a) (+ 1) 
    else modifyIORef (p a) (subtract 1) 
    readIORef $ p a 

z :: A -> IO Bool 
z = fmap not . readIORef . q 

main :: IO() 
main = do 
    a1 <- newA 
    a2 <- newA 
    m <- newIORef =<< y a1 
    modifyIORef m . (+) =<< y a1 
    n <- z a2 
    m' <- readIORef m 
    pa1 <- readIORef $ p a1 
    qa1 <- readIORef $ q a1 
    pa2 <- readIORef $ p a2 
    qa2 <- readIORef $ q a2 
    print (m', n, pa1, qa1, pa2, qa2) 

このプログラムは、上記のプログラムと同じことを行いますが、変数が変更されています。非常にまれな状況を除いて、このようなコードを書いてはいけません。

+0

例をありがとう。実際、最初のコードには構文上の問題があります。 "p = 0;" "p = 0"でなければなりません。この修正を行った後、コードは実際には2番目のコードと同じではなく-1(False、-1、False、4、True)を出力します。 – R71

+0

@Rog、申し訳ありません、私は古いコードを貼り付ける必要があります。最初の例の正しいコードは、ここにあります。 – dflemstr

+0

ご協力いただきありがとうございます。今度はこれらのコードをすべて比較し、私の問題に対する最良のアプローチを見つけようとします。 – R71

関連する問題