前回 ポケモンのモデル化とその進化を Maybe モナドと bind を使って記述しました。 変換処理を連続で適用するという意味では、前回の段階では、コードが読みやすくなった程度で モナドを使う利点がさほどない状態でした。 今回は、ポケモン進化モデルのルールを追加し、モナドを使ううれしさを探ってみます。
キャタピーのレベルに応じた進化を考えてみます。 設定は以下のようにします。
と、ここまでが前回と同じルールです。さらに以下のルールを追加します。
それでは、これをコードにします:
data Pokemon = Caterpie Int | Metapod Int | Butterfree Int
instance Show Pokemon where
show pokemon = toString pokemon where
toString (Caterpie l) = "Caterpie Level=" ++ (show l)
toString (Metapod l) = "Metapod Level=" ++ (show l)
toString (Butterfree l) = "Butterfree Level=" ++ (show l)
-- トレーニングとバトル
train :: Pokemon -> Pokemon
train (Caterpie l)
| (l+1)>=5 = Metapod $ l+1
| otherwise = Caterpie $ l+1
train (Metapod l)
| (l+1)>=10 = Butterfree $ l+1
| otherwise = Metapod $ l+1
train (Butterfree l) = Butterfree $ l+1
fight :: Pokemon -> Pokemon
fight (Caterpie l)
| (l+3)>=5 = Metapod $ l+3
| otherwise = Caterpie $ l+3
fight (Metapod l)
| (l+3)>=10 = Butterfree $ l+3
| otherwise = Metapod $ l+3
fight (Butterfree l) = Butterfree $ l+3
-- トレーニングとバトルのモナド版
trainM :: Pokemon -> Maybe Pokemon
trainM p = Just (train p)
fightM :: Pokemon -> Maybe Pokemon
fightM p
| even $ level (fight p) = Nothing
| otherwise = Just (fight p)
where
level :: Pokemon -> Int
level (Caterpie l) = l
level (Metapod l) = l
level (Butterfree l) = l
-- ポケモンゲットだぜ
getPokemon :: Pokemon
getPokemon = Caterpie 1
getPokemonM :: Maybe Pokemon
getPokemonM = Just getPokemon
これを GHCi にロードしてプレイしてみます:
$ ghci
> :load pokemon.hs
> myPokemonM = getPokemonM
> myPokemonM
Just Caterpie Level=1
ポケモン(モナド版)をゲットして確認しました。レベル1のキャタピーを手にいれています。 続いてトレーニングしたりバトルしてみます:
> myPokemonM >>= trainM
Just Caterpie Level=2
> myPokemonM >>= trainM >>= fightM
Just Metapod Level=5
> myPokemonM >>= trainM >>= fightM >>= fightM
Nothing
おっと。戦闘不能になった。何が起きた?
ということです。
もう少しいくつかプレイしてみます。
> myPokemonM >>= fightM >>= trainM >>= trainM
Nothing
> myPokemonM >>= fightM >>= trainM
Nothing
> myPokemonM >>= fightM
Nothing
ゲットしてすぐバトルするとあと何をしても Nothing です。 つまり、最初に fightM したときに Nothing になっている。
このようにモナドで bind していくと、連続変換中のどこで Nothing になろうがとりあえずコードは続けて書くことができる、という特徴があります。
もし、ゲットしたキャタピーがレベル 2 だった場合を考えてみましょう。
> myAnotherPokemonM = Just $ Caterpie 2
> myAnotherPokemonM
Just Caterpie Level=2
このポケモン myAnotherPokemonM で試しましょう。
> myAnotherPokemonM >>= fightM >>= trainM >>= trainM
Just Metapod Level=7
先ほどと育成方法は同じ(バトル トレーニング トレーニング)ですが、今度は Nothing ではなく、トランセル(Metapod)レベル 7 になりました。
せっかくなので、バタフリーまで進化させましょう。
> myPokemonM >>= trainM >>= fightM >>= trainM
Just Metapod Level=6
> myPokemonM >>= trainM >>= fightM >>= trainM >>= fightM
Just Metapod Level=9
> myPokemonM >>= trainM >>= fightM >>= trainM >>= fightM >>= trainM
Just Butterfree Level=10
うまくバタフリーまで進化できました。
一度ここでコードを整理します。 先ほどうまくバタフリーまで進化できた育成方法は takeCare 関数にしておきます。
takeCare :: Maybe Pokemon -> Maybe Pokemon
takeCare p = p >>= trainM >>= fightM >>= trainM >>= fightM >>= trainM
コード全体 pokemon.hs:
data Pokemon = Caterpie Int | Metapod Int | Butterfree Int
instance Show Pokemon where
show pokemon = toString pokemon where
toString (Caterpie l) = "Caterpie Level=" ++ (show l)
toString (Metapod l) = "Metapod Level=" ++ (show l)
toString (Butterfree l) = "Butterfree Level=" ++ (show l)
train :: Pokemon -> Pokemon
train (Caterpie l)
| (l+1)>=5 = Metapod $ l+1
| otherwise = Caterpie $ l+1
train (Metapod l)
| (l+1)>=10 = Butterfree $ l+1
| otherwise = Metapod $ l+1
train (Butterfree l) = Butterfree $ l+1
fight :: Pokemon -> Pokemon
fight (Caterpie l)
| (l+3)>=5 = Metapod $ l+3
| otherwise = Caterpie $ l+3
fight (Metapod l)
| (l+3)>=10 = Butterfree $ l+3
| otherwise = Metapod $ l+3
fight (Butterfree l) = Butterfree $ l+3
-- トレーニングとバトルのモナド版
trainM :: Pokemon -> Maybe Pokemon
trainM p = Just (train p)
fightM :: Pokemon -> Maybe Pokemon
fightM p
| even $ level (fight p) = Nothing
| otherwise = Just (fight p)
where
level :: Pokemon -> Int
level (Caterpie l) = l
level (Metapod l) = l
level (Butterfree l) = l
-- 育成
takeCare :: Maybe Pokemon -> Maybe Pokemon
takeCare p = p >>= trainM >>= fightM >>= trainM >>= fightM >>= trainM
-- ポケモンゲットだぜ
getPokemon :: Pokemon
getPokemon = Caterpie 1
getPokemonM :: Maybe Pokemon
getPokemonM = Just getPokemon
myPokemonM = getPokemonM
GHCi で確認:
> :reload
> takeCare myPokemonM
Just Butterfree Level=10
もし、レベル 2 のキャタピーで育成をはじめたらどうなる?
> myAnotherPokemonM = Just $ Caterpie 2
> takeCare myAnotherPokemonM
Nothing
戦闘不能(Nothing)になりました。
どの段階で Nothing になったのだろうか?
Maybe モナドなポケモンを takeCare すると、うまくバタフリーまで進化できたり、戦闘不能(Nothing)になったりします。
どの段階で進化したり、戦闘不能になったのでしょうか。 これをわかるようにすることはできないのでしょうか?
もしJavaなど記述した場合は、コードの途中に適宜 System.out.println などをすればよいわけです(一つの方法としては)。 Haskell でそのような処理をするには Writer モナドを使うとよいらしい。 試してみます。
(書きかけです)