2

http://www.haskell.org/haskellwiki/State_Monadの具体的な例は、モナドを使った実際のコードの書き方を理解するのに非常に役立ちます (stackoverflow/9014218 も参照)。しかし、私たち新入生のほとんどは OO のバックグラウンドを持っているため、OO プログラムを haskell にマッピングすると、同等の haskell コードを作成する方法を示すのに役立ちます。(はい、2 つのパラダイムはまったく異なります。オブジェクト指向スタイルのコードを直接 haskell に変換するのは賢明ではありませんが、これはチュートリアルとして 1 回だけです。)

オブジェクトの 2 つのインスタンスを作成し、それぞれのメンバー変数を変更するメンバー関数を呼び出し、最後にそれらを出力する OO スタイルのコードを次に示します。haskell 状態モナドを使ってこれをどのように書くのでしょうか?

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;
4

2 に答える 2

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) }

そして、私はおそらく手動のゲッター/セッターを気にせず、レンズを使用するだけです。

{-# 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

しかし、それは私が本当に書きたいことではありません。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

これで、同じrunものをさまざまなタイプAとに再利用できBます。

于 2012-01-28T02:36:01.603 に答える
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

このプログラムは以下を出力します。

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

m新しいバージョンのandを格納するために新しい変数を作成する必要があることに注意してください(毎回最後a1に追加しました)。'Haskell には言語レベルの変更可能な変数がないため、そのために言語を使用しようとするべきではありません。

IO 参照を使用して可変変数を作成することができます。

ただし、次のコードは、Haskeller の間で非常に悪いコーディング スタイルと見なされていることに注意してください。もし私が教師で、このようなコードを書いた生徒がいたら、私は課題で合格点を与えません。もし私がこのようなコードを書いた 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)

このプログラムは、上記のプログラムと同じことを行いますが、可変変数を使用します。繰り返しますが、非常にまれな状況を除いて、このようなコードを記述しないでください。

于 2012-01-28T02:37:54.957 に答える