7

純粋関数の考え方以外は、関数型プログラミングについてはほとんど知りません。John Carmack の 2013 Quakecon トークで、彼はゲームに関連する関数型プログラミングについてよく尋ねられる質問の 1 つに言及しました: 状態にアクセスできない場合、どのように銃を発砲し、他のプレイヤーにダメージを与えることができますか? (言い換え) イベント システムにはまだ状態が必要なように思われるので、イベント システムについて言及しましたが、これはよくわかりませんでした。

純粋に関数型の言語でこれをどのように達成しますか?

4

3 に答える 3

8

私のお気に入りの引用の 1 つを繰り返すには

... 世界の状態を取り込み、新しい世界を返すため、純粋なままです。

これは、Haskell のいとこである Clean について話していましたが、それでも関連しています。その要点は、その通りです。ある種の状態が必要ですが、変更可能である必要はありません。検討

myFun :: StateOfTheWorld -> a -> (StateOfTheWorld, b)

そのため、状態を変更するのではなく、新しい状態を生成するだけです。世界の同じ状態と同じアクションが与えられた場合、同じものが返されるため、これは参照透過的です。

あなたのために、あなたは次のようなものを持っているかもしれません

 killPlayer :: Game -> Event -> Game
 killPlayer g (Kill x) = g { isDead = x : isDead g }

これは、レコードの機能更新を使用しているだけです。これは少し不格好なので、次のようにするかもしれません

 killPlayer :: Game -> Event -> Action
 killPlayer (PlayerDamaged x amount) = if playerHealth g x <= amount
                                       then KillPlayer x
                                       else ReduceHealth x amount

したがって、完全なゲームの状態ではなく、違いを返すだけです。

これは機能しますが、醜いです。そこで、do記法と Control.Monad.State でこれをきれいにします。これは恐ろしく聞こえるかもしれませんが、構文をもう少し抽象化しただけで、まさに上で行っていたことです。実際、これはIOGHCにもあります。あなたがモナドについて学んだかどうかはわかりませんが、State モナドはしばしば動機付けの例です。

最後にゲームに戻ると、私が見たゲームフレームワークの多くは次のようなものです: イベントをリッスンし、ゲームの状態に小さな増分変更を提案し、異なるものを返します。最後に、フレームワーク自体が適切な openGL 呼び出しを行います。またはそれらの変更を実装するもの。

于 2013-08-03T12:46:14.183 に答える
1

私のコメントをサポートするために、http://www.haskellforall.com/2013/05/program-imperatively-using-haskell.htmlから適応した例を次に示します。

-- -----------------------------------------------------------------------------
-- * Our homegrown state monad (use @State@ from the MTL package in production).

-- | @State@ is a function (lets call it "state-updater") which "updates" a
-- state @s@ and returns some associated result @r@.
newtype State s r = State { run :: s -> (r, s) }

-- | This state-updater function is a monad.
instance Monad (State s) where

  -- | Build a state-updater which returns @x@ and don't change the state.
  return x = State $ \st -> (x, st)

  -- | From a state-updater @m@ and a function @f@ which returns a state-updater
  -- we can build a new (lazy) state-updater by performing update actions of this two
  -- state-updaters.
  m >>= f = State $ \st -> let (x, st') = run m st in run (f x) st'

-- | Simply swap the state.
put :: s -> State s ()
put st = State $ const ((), st)

-- | Get the current state as a result of this state-updater.
get :: State r r
get = State $ \st -> (st, st)

-- -----------------------------------------------------------------------------
-- * An example.

-- | Player with its health.
newtype Player = Player { _health :: Int } deriving ( Show )

-- | Game of two players.
data Game = Game { _player1 :: !Player, _player2  :: !Player } deriving ( Show )

-- | Starting from weak and strong players.
initialState :: Game
initialState = Game (Player 10) (Player 20)

-- | First player hit second.
hit12 :: State Game ()
hit12 = do
  g@(Game _ p2@(Player health)) <- get
  put g { _player2 = p2 { _health = health - 1 } }

-- | Second player hit first.
hit21 :: State Game ()
hit21 = do
  g@(Game p1@(Player health) _) <- get
  put g { _player1 = p1 { _health = health - 1 } }

-- | Test it.
test :: ((), Game)
test = run (do { hit12; hit12; hit12; hit21 }) initialState
-- 
-- initialState
-- =>
-- Game {_player1 = Player {_health = 10}, _player2 = Player {_health = 20}}
-- 
-- snd test
-- =>
-- Game {_player1 = Player {_health = 9}, _player2 = Player {_health = 17}}
-- 

レンズは書くことができます

hit12 = player2.health -= 1

hit21 = player1.health -= 1

状態トランスフォーマー(とにかく使用する必要があります) を使用すると、別のモナド ( などIO) をに混在させることができますStateが、基本的にはすべて純粋であり、次のように機能します。

... 世界の状態を取り込み、新しい世界を返すため、純粋なままです。

引用された別のコメントとして。

于 2013-08-03T14:12:23.077 に答える