131
60

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ワイのElmデビュー【後編】

Last updated at Posted at 2019-01-27

前編はこちらやで。
ワイのElmデビュー【前編】

前回のあらすじ

純粋関数型言語Elmで「ワイと敵が戦うゲーム」を作ってみることにしたワイ
果たして、砕けた床の修理代は誰が支払うことになるのか・・・!?

攻撃ボタンをクリックしたら、敵のHPを減らしたい

ワイ「ええーと、こんな感じでワイの攻撃ボタンonClick属性をつけたから・・・」

button [ onClick WaiAttack ] [ text "攻撃" ]

ワイ「敵さんの攻撃ボタンにもonClick属性をつけとこか」
ワイ「値はTekiAttackでええわ」

button [ onClick TekiAttack ] [ text "攻撃" ]

ワイ「こうしておけば、ワイ敵さん攻撃ボタンがクリックされたときに」
ワイ「それと連動してキャラクターの状態を更新できるはずなんや」
ワイ「その更新内容update関数の中に書くんやな」

update msg model =
    -- ここに処理内容を書く

ワイ「update関数は、msgmodelっちゅう2つの引数を受け取るんやな」

ワイ「modelいう引数にはワイと敵の状態が入ってくるんや」
ワイ「msgには何が入ってくんねやろ」
ワイ「なになに」
ワイ「なるほどな」
ワイ「msgにはWaiAttackもしくはTekiAttackっちゅうメッセージが入ってくるんやな」
ワイ「ワイの攻撃ボタンをクリックした場合はWaiAttackというメッセージが」
ワイ「敵さんの攻撃ボタンをクリックした場合はTekiAttackいうメッセージが」
ワイ「msgいう引数の中に入ってくるんやな」

ワイ「ちゅうことはmsg内容によって
ワイ「敵のHPを減らす処理をするのか、ワイのHPを減らす処理をするのかを分岐させればええ訳やから・・・」

    case msg of
        WaiAttack ->
            -- ここに、敵のHPを減らす処理を書く

        TekiAttack ->
            -- ここに、ワイのHPを減らす処理を書く

ワイ「こうやな!」

ワイ「ほな、そのHPを減らす処理を書いていこか」

ワイ「敵のHPを減らすのもワイのHPを減らすのも」
ワイ「HPを減らすいう意味では同じやから、関数として共通化できそうやな」

ワイ「ほな、HPを減らすための関数を書いてこか」
ワイ「HPを減らすいうんは、要は攻撃のことやから」
ワイ「関数の名前はattackにしておこか」

ワイ「まずは型注釈から書くんや」
ワイ「型注釈いうんは、関数の使い方に関する注釈や」
ワイ「今回は・・・」
ワイ「attackいう関数は、キャラクター数値を受け取って、新しい状態のキャラクターを返しますよ、と」

attack : Character -> Int -> Character

ワイ「こうやな」
ワイ「しかもこれがただの注釈やなくて、強制力をもった注釈なんや」
ワイ「この注釈の通りに実装していかんと、コンパイルエラーで怒られるんや」

ワイ「こんな関数を作るで〜いうて型注釈を書くことで」
ワイ「思考実際のコードに落とし込んでから関数を書くことになるから」
ワイ「ワイのようなザコーダーでも訳が分からなくなりにくいな」

ワイ「ほなattack関数の中身を書いていこか」
ワイ「attack関数は、キャラクターダメージを与える関数やから」
ワイ「characterdamageという引数を受け取ることにしよか」

attack character damage =
    -- ここに処理内容を書く

ワイ「こうやな」
ワイ「ほんで、キャラクターHPdamageの分だけ減らして」
ワイ「HPが減った後のキャラクター戻り値として返してやるんやから・・・」
ワイ「まずは」
ワイ「let定義してやるんや」
ワイ「新しいhpの値は、キャラクターのHPからダメージ量の分を差し引いた値ですよ、と」

    let
        newHp =
            character.hp - damage

ワイ「こうやな」
ワイ「ほんで、次はletと対になるinや」
ワイ「キャラクターHPnewHp差し替えたやつ1を」
ワイ「戻り値として返してあげますよ、と」

    in
    { character | hp = newHp }

ワイ「こうや」
ワイ「最後に評価された値が自動的に戻り値として返されるから、特にreturnとかは要らへんのやな」

ワイ「よっしゃ」
ワイ「キャラクターダメージ量を受け取って」
ワイ「新しい状態のキャラクターを返す関数」
ワイ「attackの出来上がりや!」

-- attack関数
attack : Character -> Int -> Character
attack character damage =
    let
        newHp =
            character.hp - damage
    in
    { character | hp = newHp }

ワイ「ほな次は、このattack関数を」
ワイ「update関数の中で使っていくねんな」

ワイ「まずはupdate関数型注釈がどないなってんのか見ておこか」

update : Msg -> Model -> Model

ワイ「なるほど」
ワイ「Msg型Model型の引数を受け取って」
ワイ「Model型の値を返してやればええんやな」
ワイ「型注釈が書いてあると分かりやすいな・・・」

ワイ「ほな処理を書いていこか」
ワイ「メッセージがWaiAttackの場合は、」
ワイ「ワイの攻撃力のぶんだけ敵さんにダメージを与えるんや」
ワイ「つまり、さっき作ったattack関数敵さんワイの攻撃力を渡してやればええんや」
ワイ「第一引数が敵さんで」
ワイ「第二引数がワイの攻撃力やから」

attack model.teki model.wai.offensivePoint

ワイ「こうやな」
ワイ「attack関数が返してくるのは攻撃された後の敵さんやから、」
ワイ「attackedTekiいう名前にしよか」

let
    attackedTeki =
        attack model.teki model.wai.offensivePoint

ワイ「こうやな」
ワイ「ほんで、その攻撃された敵さんを」
ワイ「modelの中の古い敵さんと差し替える感じや1

in
{ model | teki = attackedTeki }

ワイ「逆に、メッセージがTekiAttackの場合は、」
ワイ「敵さんの攻撃力のぶんだけワイにダメージを与えるから」

let
    attackedWai =
        attack model.wai model.teki.offensivePoint
in
{ model | wai = attackedWai }

ワイ「こうやな」
ワイ「update関数の出来上がりや!」

update : Msg -> Model -> Model
update msg model =
    case msg of
        WaiAttack ->
            let
                attackedTeki =
                    attack model.teki model.wai.offensivePoint
            in
            { model | teki = attackedTeki }

        TekiAttack ->
            let
                attackedWai =
                    attack model.wai model.teki.offensivePoint
            in
            { model | wai = attackedWai }

ワイ「なんか、結局同じようなコードを2回書いてしまった感じがするな」
ワイ「初めてやからしゃあないわ」
ワイ「あとでリファクタリングしよか」

ワイ「とりあえずこれで攻撃ボタンをクリックしたら」
ワイ「それと連動して画面が再描画されるんちゃうか?」

ワイ「攻撃ボタンをポチポチ、と」
ワイ「おお、ちゃんと動いとる!
ワイ「攻撃するたびに敵やワイのHPが減っていくで!」

ワイ「あ、攻撃し過ぎるとHPがマイナスの値になってしまうな」
ワイ「でもそれも、どうやって直すか大体わかるで」
ワイ「attack関数の中でif式を書けばええんや」
ワイ「残HPの量より大きいダメージを受け取った場合は」
ワイ「キャラクターのHPが0になるようにするだけや」

newHp =
    if character.hp > damage then
        character.hp - damage

    else
        0

ワイ「できたで!

Elmを触ってみた感想

ワイ「型システムが優秀やから、間違ったことしてたらコーディング中に教えてくれんねん」
ワイ「せやから、実行時に〇〇がundefinedやで!みたいなエラーが出ることが無かったな」

ワイ「型注釈を書くのは必須ではないんやけど、」
ワイ「明示的に型を書いていくことで頭の中の設計をコード化できている感じがして思考が整理されるし」
ワイ「あとからコードを見直したときにも何をしようとしているか分かりやすいな」

ワイ「せやからワイのようなザコーダーでも、作ろうと思ったもんがサクッと作れたで」
ワイ「・・・今のところ何のゲーム性もないようなゲームやけどな」

ワイ「えっと、作り方の流れとしては・・・」
ワイ「まず管理していきたい状態Model型として定義する」
ワイ「ほんで、モデル=状態初期値initとして設定。」
ワイ「それを、view関数を使って表示させんねんな」

ワイ「ほんで、viewの中のボタンにonClick WaiAttackとか書いとけば、」
ワイ「そのボタンをクリックしたのと連動してupdate関数WaiAttackっていうメッセージが来るから」
ワイ「WaiAttackの場合はワイの攻撃力のぶんだけ敵のHPを減らすんや!
ワイ「ってなことをupdate関数の中で書けばええんやな。」

ワイ「そしたらそのupdate関数が返した新しい状態が」
ワイ「なんやかんやでview関数に渡ってきて」
ワイ「画面が再描画されるわけや」

ワイ「こんな感じで、再代入いう概念がなくても状態の変化を表現できんねんなー」
ワイ「これがThe Elm Architectureいうやつか」

ワイ「状態を変更する処理がupdate関数の中に集まる形になるから」
ワイ「一括管理できてる感じがええな」

ワイ「いろんなオブジェクトがアプリケーションの状態を変え放題やったりすると」
ワイ「思わぬ作用が起こって」
ワイ「1回攻撃しただけなのにHPが2回分減っとった!」
ワイ「なんてことも起こりうるからな」
ワイ「さすが、ReduxVuexにも影響を与えたElmちゃんやな」

ワイ「うーん、なんやろ・・・楽しかったなぁ
ワイ「仕事でも使いたいわぁ・・・」

仕事でElmを使うために

ワイ「ほな、まずは会社のみんなにコンビニのコーヒーでも奢って
ワイ「精神的買収をしとこか」
ワイ「そういう根回しは大切や」
ワイ「よ〜し、エエとこ見せるで!

ワイ「お〜い、みなさん」
ワイ「ちょっとコーヒーでも買ってきますわ」
ワイ「ええて!ええて!ワイが奢りますさかい!」
ワイ「ええと、何人分や?」
ワイ「0, 1, 2, 3・・・3人分やな!

ハスケル子「やめ太郎さん」
ハスケル子「現実のものを0から数えないでください
ハスケル子「1つ足りなくなります」

社長「(やっぱこいつアホやな・・・)」

〜Fin〜

後日談: 砕けた床はどうなったか

社長「下のフロアもよく見えるし、このままでええわ!」

追記

仕事でElm書きたいクラスタを作ってみました。よかったらご参加くださいやで。

  1. 実際には既存のレコードの一部を差し替えるのではなく、新しいレコードが生成されます。レコードとはJavaScriptでいうオブジェクトのようなものです。 2

131
60
12

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
131
60

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?