23
21

More than 5 years have passed since last update.

Haskellでオブジェクト指向プログラミング

Last updated at Posted at 2013-03-04

Lensを活用すれば、オブジェクト指向すら表現できる…これが、Haskellの力なのか…

クラスはデータ型、メソッドはインスタンスの状態を変更するStateモナド、インスタンスはIORefに対応している。

{-# LANGUAGE TemplateHaskell, Rank2Types, ExistentialQuantification #-}
import Control.Lens
import Control.Applicative
import Control.Monad.State
import Control.Monad.IO.Class
import Data.IORef

infix 9 .-
infix 9 .!

data Vec2 = Vec2 Float Float deriving Show

(&+) :: Vec2 -> Vec2 -> Vec2
Vec2 x0 y0 &+ Vec2 x1 y1 = Vec2 (x0 + x1) (y0 + y1)

newtype Default a = Default { getDefault :: a } deriving (Show)

ofDefault :: Iso (Default a) (Default b) a b
ofDefault = iso getDefault Default

-- 指定されたLensに対するメソッドの呼び出し
(.-) :: MonadState s m => Lens' s c -> StateT c m a -> m a
l .- m = do
    s <- get
    (a, s') <- zoom l m `runStateT` s
    put s'
    return a

-- IORefに対するメソッドの呼び出し
(.!) :: MonadIO m => IORef c -> StateT c m a -> m a
ref .! m = do
    s <- liftIO $ readIORef ref
    (a, s') <- runStateT m s
    liftIO $ writeIORef ref s'
    return a

data Obj = Obj -- 基底クラスのメンバー変数
    { _position :: Vec2
    , _velocity :: Vec2
    , _name :: String
    } deriving Show

makeClassy ''Obj

instance HasObj a => HasObj (Default a) where obj = ofDefault . obj

class Objs c where -- 基底クラスのメソッド
    update :: MonadState c m => m ()
    draw :: (MonadState c m, MonadIO m) => m ()

instance HasObj c => Objs (Default c) where -- メソッドの実装
    update = do
        v <- use velocity
        p <- use position
        position .= v &+ p
    draw = liftIO . print =<< use position 

data ObjA = ObjA -- クラスの継承(メンバー変数の拡張)
    { _superObjA :: Default Obj
    , _accel :: Vec2
    }

makeLenses ''ObjA

instance HasObj ObjA where obj = superObjA . ofDefault

instance Objs ObjA where
    update = do
        a <- use accel
        v <- use velocity
        velocity .= a &+ v

        superObjA .- update -- スーパークラスのメソッドの呼び出し

    draw = superObjA .- draw

class Objs c => ObjsA c where -- クラスの継承(メソッドの拡張)
    reset :: MonadState c m => m ()

instance ObjsA ObjA where
    reset = superObjA . velocity .= Vec2 0 0

data WrapObjs = forall a. (Objs a, HasObj a) => WrapObjs (IORef a)

test = do
    a <- newIORef $ Default $ Obj { _position = Vec2 0 0, _velocity = Vec2 1 1 , _name = "Hoge" }
    b <- newIORef $ ObjA
        { _superObjA = Default $ Obj { _position = Vec2 10 0, _velocity = Vec2 (-1) (-4), _name = "Fuga" }
        , _accel = Vec2 0 0.5 }
    let objs = [WrapObjs a, WrapObjs b]
    forM_ [1..10] $ \_ -> do 
        forM_ objs $ \(WrapObjs i) -> do
            putStr =<< i .! use name
            putStr " "
            i .! update
            i .! draw
23
21
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
21