Elmのパターンマッチと型を使ってcounterサンプルをまた改造した

nekoroll.hatenablog.com

を更に改造するぞい

こんな事を教えてもらった

ABAB↑↓BA先生いつもありがとうございます
満へぇなので金の脳を贈ります

実際にやってみた

f:id:nekorollykk:20200821004059p:plain

こちらが確認のVTRです(八嶋智人)
ellie-app.com

やったこと

Msgのアクションを一つに

-  = Increment
-  | Decrement
-  | IncrementDouble
-  | DecrementDouble

+  = IncrementN Int

正負両方の任意の値を受けるアクションにした

updateのパターンマッチも修正

-  Increment ->
-      { model | count = model.count + 1 }
-
-  Decrement ->
-      { model | count = model.count - 1 }
-
-  IncrementDouble ->
-      { model | count = model.count + 2 }
-  
-  DecrementDouble ->
-      { model | count = model.count - 2 }

+  IncrementN n ->
+      { model | count = model.count + n }

引数を受けるようにした
Doulble, Triple...と増え続ける地獄を回避できた

viewのonClickも修正

-  [ button [ onClick Increment ] [ text "+1" ]
-  , button [ onClick IncrementDouble ] [ text "+2" ]
-  , button [ onClick Decrement ] [ text "-1" ]
-  , button [ onClick DecrementDouble ] [ text "-2" ]

+  [ button [ onClick (IncrementN 1) ] [ text "+1" ]
+  , button [ onClick (IncrementN -1) ] [ text "-1" ]

引数を渡せるようにした

ここでミスをして一度エラーになっていた

+ [ button [ onClick IncrementN 1 ] [ text "+1" ]
+ , button [ onClick IncrementN -1 ] [ text "-1" ]

見ておわかりの通りIncrement nだから引数渡すだけ
と愚直に追加していたのである
その結果以下のエラーだった

Too Many Args
Line 33, Column 20
The `onClick` function expects 1 argument, but it got 2 instead.

33|         , button [ onClick IncrementN -1 ] [ text "-1" ]
                       ^^^^^^^
Are there any missing commas? Or missing parentheses?
Too Many Args
Line 31, Column 20
The `onClick` function expects 1 argument, but it got 2 instead.

31|         [ button [ onClick IncrementN 1 ] [ text "+1" ]
                       ^^^^^^^
Are there any missing commas? Or missing parentheses?

意訳

そこのお前!onClickが期待してる引数は1つだぜ!  

f:id:nekorollykk:20200821005509p:plain

親切すぎるエラーである
ここで「()で囲むんだった」とABAB↑↓BA先生のコードを思い出し解決
javaだとわかるけどわからないエラーを吐き出して死んでいる所だが
Elmは親切に

  • 引数多いよ!
  • ここだよ!ここ!
  • もしかして,()忘れてない?

と波線までつけて教えてくれた
優しすぎるコンパイラである、きっと中にガンジーが入っているのだろう
ラブアンドピース!平和が一番!

完成…と見せかけて

ここまでだとコンパイラが親切で優しいだけのjavascriptと変わらないのである
そこでこちらの教えを実践する


(自分のツイート貼るの恥ずかしい)

Intだと広すぎるのでより厳密にする

やりたいこと

  • Intだと広すぎる(-2147483647~2147483647)
    Basics - core 1.0.5
  • -2, -1, 1, 2のみを許容する型にしたい

その1 カスタム型を作成してみる

ellie-app.com

-2~2を定義したカスタム型を作成

type IncrementNInt= PlusOne
                | PlusTwo
                | MinusOne
                | MinusTwo

PlusOne, PlusTwoとかはVariant(バリアント)っていうらしい

Msg型の引数の型を変更

-  = IncrementN Int
+  = IncrementN IncrementNInt

さっき作ったカスタム型に変更する
これでIncrementNは定義4つのVariant以外受け入れない引数になった

パターンマッチを型に合わせて変更

-        { model | count = model.count + n }

+  case n of
+      PlusOne ->
+          { model | count = model.count + 1 }
+      PlusTwo ->
+          { model | count = model.count + 2 }
+      MinusOne ->
+          { model | count = model.count - 1 }
+      MinusTwo ->
+          { model | count = model.count - 2 }

viewを変更

-  [ button [ onClick (IncrementN 1) ] [ text "+1" ]
-  , button [ onClick (IncrementN -1) ] [ text "-1" ]

+  [ button [ onClick (IncrementN PlusOne) ] [ text "+1" ]
+  , button [ onClick (IncrementN PlusTwo) ] [ text "+2" ]
+  , button [ onClick (IncrementN MinusOne) ] [ text "-1" ]
+  , button [ onClick (IncrementN MinusTwo) ] [ text "-2" ]

直接数値を定義するのではなく、用意したVariantを渡すようにした

動いた

エラーが起きるか色々試す

+3を追加しちゃう

+  , button [ onClick (IncrementN 3) ] [ text "+3" ]

怒られた

Type Mismatch
Line 44, Column 40
The 1st argument to `IncrementN` is not what I expect:

44|         , button [ onClick (IncrementN 3) ] [ text "+3" ]
                                           ^
This argument is a number of type:

    number

But `IncrementN` needs the 1st argument to be:

    IncrementN

Hint: Only Int and Float values work as numbers.

意訳

IncrementNの第一引数にnumber渡してるけど
IncrementNの第一引数はIncrementNInt型やぞ

エラー吐いてえらい!(コウペンちゃん)

存在しないVariantを追加しちゃう

+  , button [ onClick (IncrementN PlusThree) ] [ text "+3" ]

怒られた

Naming Error
Line 44, Column 40
I cannot find a `PlusThree` variant:

44|         , button [ onClick (IncrementN PlusThree) ] [ text "+3" ]
                                           ^^^^^^^^^
These names seem close though:

    PlusOne
    PlusTwo
    Just
    True

Hint: Read <https://elm-lang.org/0.19.1/imports> to see how `import`
declarations work in Elm.

意訳

そんなVariantねンだわ
PlusOne, PlusTwoってやつが近いけどもしかしてこれにしたかった?

親切すぎる
git checkotu って書いた時に

git: 'checkotu' is not a git command. See 'git --help'.

The most similar command is
        checkout

って言ってくれるより親切

その2 パターンマッチで数値を絞る

ellie-app.com

Msgは特にいじらず

type Msg
    = IncrementN Int

パターンマッチにそのまま追加

+    1 ->
+        { model | count = model.count + n }
+    2 ->
+        { model | count = model.count + n }
+    -1 ->
+        { model | count = model.count + n }
+    -2 ->
+        { model | count = model.count + n }
+    _ ->
+        { model | count = model.count + 0 }

intのパターンマッチで、-2~2以外は未使用なので
_ワイルドカードを定義した

viewは既存に+2,-2を追加

+  , button [ onClick (IncrementN 2) ] [ text "+2" ]
+  , button [ onClick (IncrementN -2) ] [ text "-2" ]

死んだ

Unexpected Symbol
Line 30, Column 17
I ran into an unexpected symbol:

30|                 -1 ->
                    ^
I was not expecting to see a - here. Try deleting it? Maybe I can give a better
hint from there?

意訳

なんか急に`-`出てきたんだけどなにこれ?
これミスなら削除してくれ

f:id:nekorollykk:20200821031022p:plain

順番変えてみた

-1 ->
    { model | count = model.count + n }
-2 ->
    { model | count = model.count + n }
1 ->
    { model | count = model.count + n }
2 ->
    { model | count = model.count + n }
_  ->
    { model | count = model.count + 0 }

マイナスを先頭にした

死んだ

Unexpected Symbol
Line 26, Column 17
I ran into a minus sign unexpectedly in this pattern:

26|                 -1 ->
                    ^
It is not possible to pattern match on negative numbers at this time. Try using
an `if` expression for that sort of thing for now.

意訳

今の所パターンマッチでマイナス使えねンだわ
if使ってくれや

f:id:nekorollykk:20200821031316p:plain

一応調べてみたらこんなんありました github.com

I've bumped into this as well, trying to decode a JSON payload where -1 is used as a flag.

But practical examples aside, it just seems odd in principle that the language will allow you to pattern match against integers, unless those integers happen to be negative.

意訳(間違ってたらごめんちゃい)

再現した、そもそも整数パターンマッチってどうなの?

(ElmならVariat使ってね。ってことかな)

if使えば実現出来るのかも知れないけど、issue上でこんな意見もあるし
そもそもやろうとしていることが間違っているのかも知れない

まとめ

  • 引数をカスタム型にしてパターンマッチしたら行けた
  • 型作って厳密にするとヒューマンエラーなくなるので嬉しい
  • intで受けつつ、値を絞るイメージが合ったけど間違い?
    PHPだけどこういうコードのイメージ
<?php
const ALLOW_NUMBERS = [-2, -1, 1, 2];

public function incrementN(int $count,  int $n)
{
  if (!in_array($n, self::ALLOW_NUMBERS, true) {
    return;
  }
  return $count + n;
}