kachick / times_kachick

`#times_kachick channel in chat` as a public repository. Personal Note and TODOs
https://github.com/kachick/times_kachick/issues?q=is%3Aissue+is%3Aclosed
6 stars 0 forks source link

Elm で手習いアプリを作ってみたい => 少機能な モブプログラミングタイマー を作る #53

Closed kachick closed 1 year ago

kachick commented 3 years ago

動機

「色んな職場でつぶしがきく」的発想だと Elm を選ぶ理由は特に無いと思うのだけれど、「自分1人で作って、その後も楽にメンテしつづけたい」みたいな時には React apps 系の dependency 更新地獄にならないっぽいし、 TypeScript よりも堅く書けるという意味で美味しいのでは?と思った。

開始時点での Elm 力

そもそも Frontend 自体弱い上に、Elmに関しては 2年ぐらい前に @pankona さんとペアで6~8時間 ぐらい? https://guide.elm-lang.jp/ を読んでサンプルアプリを立ち上げて、ちょっとカスタマイズの問題やろうとしたら、すぐに引っかかって挫折した。 個人では 基礎からわかる Elm には一通り目を通して良い感じだなーと思ったんだけど何も覚えてない・・・

ぐらいなんだけど「感触は良かった」

参考資料

ログ

2021-06-26

まず https://qiita.com/arowM/items/5ec5853298fc880353b7 が最新版に更新し続けられてるっぽいので軽く目を通して、一応 discord に入っておくことにした。 beginners とかいうチャンネルが合って如何にも良さそうなので、とりあえず何か詰まったら今度はここで聞いてみたい https://discord.com/channels/388648490173202434/395574575783608321 そこで紹介されていた https://github.com/lucamug/elm-ecommerce を見て良さげだったので ⭐ 付けた。こういうのポンポン作れるようになりたい・・・しかし CSS 0 とか書いてあるけど、じゃあどうやって作ったんだろう?謎技術だ。

https://qiita.com/arowM/items/dfb38d1c5f3dfde8b8bf を読んだ。逐一納得できる。やはり自分に合うのでは・・・?

言語基礎的なところはむしろ飛ばしたかったんだけど、実践を謳っている guide でも最初にあるのでやむなく触ってみた。

https://guide.elm-lang.jp/core_language.html

ボリュームは少ないので、たしかに触って正解だと思う。 書かれては居たが、エラーメッセージの詳細さには本当に感心する。ArgumentError だったらたしかにこう表示して欲しいな〜みたいなのも全部満たしてくれて気持ちがいい。 Ruby でいうところの did_you_mean 的な物も当たり前のように用意されていた。

screen_shot 2021-06-26 14 21 25

https://guide.elm-lang.jp/architecture/buttons.html

薄い言語機能の基本をなぞった後は、いきなり The Elm Architecture に入る。さすがだなーという感じで、コンセプトはわかるものの、「Redux にめちゃくちゃ苦手意識がある」自分にその本家は受け入れられるのだろうか?

とりあえず https://guide.elm-lang.jp/architecture/buttons.html の練習問題をやってみる https://elm-lang.org/examples/buttons に リセット機能を足せというもので、いきなり躓いていきなり解決した。 ポイントは

update msg model =

のところで、 model = 0 としたのがつまづきだった。なるほど、自分の頭が関数型になってないわけだなふむふむと考えて

update : Msg -> Model -> Model

の定義を見た上で、そういえばこの Model って単に Int のエリアスだっけと気づいた。

type alias Model = Int

ということで、 0 と、単に 0 を返すだけのコードに変えたらコンパイルが通ってちゃんと動いた。いきなり躓いたのはあれだけれども、一瞬で解決できたのでむしろ気持ちがいい

これがうまくいったら、10ずつ増えるボタンを追加してみてください。

絶対こういうこと言いたいわけじゃないんだろうなーとは思いつつ、 Increment10 という message を増やして解決してしまった。多分あれでしょ、本当は message に一緒に引数を渡せるように出来て、それを学んでほしいんでしょ?と思いつつ見て見なかったことにする。後で困ったら調べよう。

https://guide.elm-lang.jp/architecture/text_fields.html

予約語のtypeについてのヒントをよく見てください

ということなので見てみた。オンラインエディタから簡単にジャンプできる事自体は嬉しい物の、 日本語ガイド -> 本家オンラインエディタ -> 本家ガイド という遷移になってしまうようで、若干面倒というか、わかってないと頑張って英語読む羽目になってしまいそうだ(ちょっと読んでしまった)。大した手間でも無いので、とりあえずは都度都度対応する日本語ガイドを探して読むようにする。

さて、ここではさっきの疑問への答えというか、メッセージに引数を渡せそうな雰囲気だ。やったー!という感じでさっきの Increment10 を Increment に step を渡せるように変えてみようとしたが・・・うまくいかない。

onClick Increment 1

-- TOO MANY ARGS ----------------------------------------------- Jump To Problem

The `onClick` function expects 1 argument, but it got 2 instead.

63|     , button [ onClick Increment 1] [ text "+" ]

                   ^^^^^^^
Are there any missing commas? Or missing parentheses?

いつもの自分なら挫折しそうだが、ここでエラーメッセージの詳細さによって即座に助けられた。

    , button [ onClick (Increment 1)] [ text "+" ]
    , button [ onClick (Increment 10)] [ text "+10" ]

Or missing parentheses? 様様である。気持ちがいい。

演習: ここからオンラインエディタへ行き、モデルの content に保持している文字列の長さを view 関数の中で表示させてみましょう。文字列の長さを取得するには String.length 関数を使います。

ちょろすぎでは?と思ってやってみたら速攻で失敗した。全く学習していない見積もりの甘さである。

The 1st argument to `text` is not what I expect:

59|     , div [] [ text (String.length model.content) ]

                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
This `length` call produces:

    Int

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

    String

Hint: Want to convert an Int into a String? Use the String.fromInt function!

なるほどなーということで、Hint様の導くまま

   , div [] [ text String.fromInt((String.length model.content)) ]

とやってみた、が怒られた。これは多分関数呼び出しの引数のルールあたりを自分がわかってないんだろうなぁと当たりをつけて適当にいくつか試してみた結果以下で通った

  , div [] [ text (String.fromInt (String.length model.content)) ]

一瞬悩んで一瞬で解決の繰り返しで気持ちのいいチュートリアルだ(まぁ、数年前に同じことやってた筈なのでアレだが・・・)

Note: このプログラムで Change の値がどんなはたらきをしているのか知りたいなら、カスタム型やパターンマッチの節を見てみてください。

これはね、ここで脱線というかそっちに気を取られるとそれで1日終わっちゃうから敢えて見ない。

https://guide.elm-lang.jp/architecture/forms.html

お、いきなり複雑というか、 view 周りに手が入ったなという印象。そしてその解説がちゃんとされている

ElmでHTMLを書くときに素敵なのは、input や div などが全てごく普通の関数だということです。これらの関数は引数として (1) 属性のリストと、(2) 子ノードのリストを受け取ります。普通の関数を使っているおかげで、ビューを組み立てるときにもElmの力を余すことなく使えるのです!

JSXが使えない事は色々言われているようだし作者も意図的だと返事をしているらしいが、記法はともかくやりたいことは似たようなもんなのかな?と思った。まぁやっぱJSXの方がHTMLっぽく読めるよなぁとは思いつつ、慣れればこれでもいけそうな気がするのでとりあえずそのまま進めよう。

viewInput t p v toMsg =

げ、自分の苦手な 短い命名推奨文化 か? と思ったけど、多分単に関数名とのバッティングを避けてるのだと思うのでこれも深く考えないで置く。

こうした補助関数をみると、HTMLのシンタックスをそのまま用いるのではなく、現状のように通常のElmコードでHTMLを表現できるHTMLライブラリの利点がわかります。すべてのコードをベタ書きでviewに書くことももちろんできますが、Elmにおいて補助関数をつくって部品をくくりだすことはいたって普通のことですので、view関数のコードだろうが補助関数を使えば良いんです。「コードがわかりづらくなってきたら、補助関数に分けてみよう!」と考えてみましょう。

いい話だ。

    練習問題: ここからオンラインエディタを開いてこの例のコードを表示し、補助関数の viewValidation に次の機能を追加してみてください。

        パスワードが8文字より長いか確認する。
        パスワードが大文字、小文字、数字を含むか確認する。

    この問題を解くときは String モジュールに用意された関数を使いましょう。

ふむ・・・

まず パスワードが8文字より長いか確認する。 これは、 String.length を既に知っているわけなので、知りたいのは関数どうこうというよりも elsif ってどうすんの? だった。 これはガイドには書かれていなかったので、公式っぽいドキュメントをあたった。 https://elm-lang.org/docs/syntax else if が正しいらしい。それだけわかれば話は解決で、以下を先頭に入れて他を else if にすることで実現できた。

  if (String.length model.password) < 8 then
    div [ style "color" "red" ] [ text "Password is too short!" ]

パスワードが大文字、小文字、数字を含むか確認する。

こいつは確かに機能を知らないと大変だろうなと思って https://package.elm-lang.org/packages/elm/core/latest/String を見に行った。 薄いと言うよりもシンプルというか必要十分な感じで、全部目を通すのにもさほど時間はかからない。 ここだけ見ると、正規表現系の機能がなさそうで、他モジュールに切り出されているのか使わない方針なのかはおいおいわかるだろうと考えて今は追わない。 indexes の alias として indices が用意されているのには、え、そここだわっちゃうの?と、ちょっとウッとなった。でもまぁこれ一個ぐらいしか alias 見当たらないし、気にしないようにする。

さて使えそうな機能は

contains : String -> String -> Bool

toList : String -> List Char

any : (Char -> Bool) -> String -> Bool

かなと辺りをつけたが、どれを選べば良いのか選定がちょっと悩ましい。 contains で 大文字、小文字、数字 を調べるのに全パターンの String というか Char を書けなんて言うのはキツイ話なのでまず捨てる。 そうすると Char の List にしてしまうとマシか?と思った。ただこれも、全パターンの Char を網羅する必要はありそうでやはり面倒くさそうだ。

そうすると結局 any に落ち着く気がする。 例示されている isDigit とかいう関数は単なる例なのか本当にあるものなのか調べてみたら、本当に Char にあるものっぽい

https://package.elm-lang.org/packages/elm/core/latest/Char

isUpper と isLower もあるので、これで解決かなという気分。

だったのだけれど、今度は「マッチしなかった時」分岐がほしいので、 unless というか boolean を反転させる機能を知らないと駄目ではと気づいた。 ! を頭につけたがコンパイルエラーだったので https://elm-lang.org/docs/syntax を調べる。 invert とか unless で探したが出てこなかったので bool で探したら答えを見つけた

True && not (True || False)

なるほど not ね。今度こそ解決だ。

と思ったら 単に isDigit を使うと そんなの無いよと怒られる。ただこれは予期してたので驚かなかった。むしろそうなってほしいというか。 import の仕方とかも出てきたが、単に Char.isDigit で、今度こそ解決

viewValidation : Model -> Html msg
viewValidation model =
  if (String.length model.password) < 8 then
    div [ style "color" "red" ] [ text "Password is too short!" ]
  else if not (String.any Char.isDigit model.password) then
    div [ style "color" "red" ] [ text "Password does not contain numbers" ]
  else if not (String.any Char.isUpper model.password) then
    div [ style "color" "red" ] [ text "Password does not contain upccase" ]
  else if not (String.any Char.isLower model.password) then
    div [ style "color" "red" ] [ text "Password does not contain downcase" ]
  else if model.password == model.passwordAgain then
    div [ style "color" "green" ] [ text "OK" ]
  else
    div [ style "color" "red" ] [ text "Passwords do not match!" ]

any 1回で 複数のチェックを巻き取れたほうが効率が・・・みたいな発想はあんましない方が良いだろうし、ここでは考えない。

注意: HTTPリクエストを扱う前にまだ学ぶことがあります。試すのはHTTPのパートまで読んでからにしましょう。適切に順を追って説明していくので、かなり簡単になるはずです。

あれ、なんで急にHTTPリクエストの話がここで出てきたんだ?とはちょっと思ったけど、バリデーションの中でサーバーに問い合わせるような用途を気にしてかなと思った。まぁ普通避けて通れない話だとは思うので、ガイドの中でカバーされてると知れただけでも安心感はある。

Note: 入力の検証を行うための汎用的なライブラリを作っても、苦労の割には大した結果を得られないでしょう。そんなライブラリを作らなくても、こうしたチェックはElmの通常の関数を使うだけで実現できる場合がほとんどだと思います。いくつかの引数をとって、Bool か Maybe を返せばいいのです。例えば、2つの文字列が等しいかどうかをチェックするのに、あえてライブラリを使う必要なんてあるんでしょうか? 我々が知る限りでは、特別に何か追加でライブラリを使わなくても、実現したい特定の状況に合わせた具体的なロジックを書くことが最もシンプルなコードにつながります。なので、より複雑な方法が必要だと判断する前に、必ずまずこの最も単純なやり方を試してみてください!

いやー良いこと言うなぁ・・・ホントそうだと思うよ。

https://guide.elm-lang.jp/types/

ここで型の話に入った。いきなり言語機能のところでここを深堀りせずに、ある程度実際のコードを触らせた後でここに持ってくるのがニクイところだ。

Elm の主な利点の 1 つは、ランタイムエラーが実際に起きないことです。 Elm コンパイラが非常に素早くソースコードを分析して、値がプログラム中でどのように使われているか解析できるためです。

おっ、凄い自信満々では?ここがまさに TypeScript じゃなくて Elm を選びたいところの一つなんだけど、正直 JavaScript へコンパイルしている時点で ランタイムエラーが実際に起きない なんて言えるのか・・・?と疑問には思うのだけれど、信じてみよう

値を間違った方法で使用すると、コンパイラがフレンドリーなエラーメッセージで警告してくれます。 これは 型推論 と呼ばれています。 コンパイラは、全ての関数の引数と返り値の型を解析します。

この威力というか恩恵には既に十分預かっている。 🙏

さて、以下 typo 検出の強さをつらつら述べてくれているのだが、この辺になってくると「REPL欲しいな・・・」と思った。でぐぐったら https://elm-lang.org/news/repl が出てきたのだが、editor に vscode がなかったり記事が2013だったり諸々古くて怪しいので GitHub に飛んでみたら archive されとる https://github.com/elm-lang/elm-repl Merged into elm/compiler ということで https://github.com/elm/compiler に飛んで見る・・・が、READMEが敢えて薄い感じ。 https://elm-lang.org/try のオンライエディタへのリンクは出てきて、まぁ元々フロントエンドに特化した言語らしいから、むしろこっちになれるかと思った。これをREPLと考える! で、その結果出てきたエラーメッセージが大分ガイドと違うので、PRをどこかで投げてはおきたい。今それをやると脱線しちゃうからね・・・ぐっと我慢。

https://guide.elm-lang.jp/types/reading_types.html

型注釈を書くのは必須ではありませんが、絶対にお勧めします。利点は次のとおりです:

    エラーメッセージの質 — 型注釈を書いておけば、あなたがそのコードで何をしようとしているかを型注釈がコンパイラに教えてくれます。あなたの実装は間違っているかもしれません。そして今コンパイラはあなたが型注釈で記述した意図と実装を比較してくれます。コンパイラ「あなたは引数powerLevelがIntだと言いましたが、Stringとして使われるようになっています!」
    ドキュメントとしての効用 — あとでコードを見直すとき(または同僚が初めて読むとき)、実装を非常に注意深く読む必要なくその関数に何が入って何が出ていくかを正確に理解するのに型注釈は本当に役に立ちます。

おー、ここまで強く勧めてくれるとむしろ気持ちが良い。そうしましょう。

型変数(タイプバリアブル)

ふむ、 List a -> Int の a への説明っぽい。詳しい人には怒られそうであれだけど、 any じゃなくて generics 的なもの。と雑に理解しておく。

Note: 型変数は小文字から始めなければなりませんが、完全な単語でも構いません。つまり例のように1文字の変数でなくても問題ありません。List.lengthの型をList value -> Intとも書けますし、List.reverseの型はList element -> List elementとも書けます。小文字で始まっていれば大丈夫です。型変数のaやbといった1文字のものは慣例によりいたるところで使われていますが、より具体的な名前を付けたほうがいい場合もあります。

お、短い命名に凄い強くこだわってる感が無くて性に合いそう。

制約付き型変数

なるほど。広げすぎないという感じっぽいので、自分でコード書くときもまずは制約付きで考えたほうが良いのかな?

ここまでで値と関数の型については非常によく網羅してきましたが、より複雑なデータ構造が必要になり始めたら型はどのようになるでしょうか?

ちょっと次のページ行くのが怖い

https://guide.elm-lang.jp/types/type_aliases.html

型エイリアス

あ、なんだ。これぐらいならわかる。ほっとした。

なお、レコードコンストラクターを使う際は、与える引数の順番が型エイリアスを定義したときのフィールドの順番と一致している必要があります。

これはちょっと不便感というか、名前付きでコンストラクト出来ない物かね・・・ まぁ一個のレコードが多くのフィールドを持ちすぎないようにしたり、型の恩恵に預かれることとか考えると気にしなくて良いのか・・・? うーん、保留。

念のため再度の確認ですが、これはレコードの場合に限った話です。レコード以外の型に対して型エイリアスを作っても、コンストラクターは作成されません。

大事。

https://guide.elm-lang.jp/types/custom_types.html

カスタム型(Custom type)

おっ、なんかいきなり難しくなった気がする・・・ まず、改善前例に上げられている方のコードは読める

type UserStatus
  = Regular
  | Visitor

type alias User =
  { status : UserStatus
  , name : String
  }

thomas = { status = Regular, name = "Thomas" }
kate95 = { status = Visitor, name = "kate95" }

改善例が、なんとなくはわかるが、諸々ショートカットされすぎてる気がして、あんましっくりこない・・・

type User
  = Regular String
  | Visitor String

thomas = Regular "Thomas"
kate95 = Visitor "kate95"

しかし、次まで行くとメリット的な物がわかる気もした。

type User
  = Regular String Int Location
  | Visitor String
  | Anonymous

なるほどー、型によって持つフィールド自体が変わるというパターンで、変に nullable なパターンみたいなのを考慮しなくてよくなるからこっちの方がいい的な感じなのかな・・・わかる気もする。

メッセージ

type Msg
  = PressedEnter
  | ChangedDraft String
  | ReceivedMessage { user : User, message : String }
  | ClickedExit

なるほど・・・ "The Elm Architecture" の Msg こそが、まさにその例だと。なるほどだなー。

Profile のデータの状態をLoadingから始めて、フェッチに失敗したらFailure、成功したらSuccessというように何が起こったかに応じて状態を遷移させることができます。このような設計はview関数を書くのを本当にシンプルにします。データの状態がカスタム型で表現されているのでview関数はデータをロードしているときも常に妥当な見た目を表示することができます。

なるほどしかり

Note: カスタム型は Elm で最も重要な機能です。 特に一度でもより厳密にシナリオを設計しようとする習慣を持てば、カスタム型に非常に深みを感じるでしょう。わたしはこの深みを付録の集合としての型や型のビット表現で共有しようと試みています。助けになれば幸いです!

最も重要!やたら手厚いなぁと思ったらそこまでか。いやー、これは心に刻んでおかないと。 ✏️

https://guide.elm-lang.jp/types/pattern_matching.html

おっ、 カスタム型を case で分岐させる例だ

そしてもし、toName (Visitar "kate95") や toName Anonymous のような不正な引数を与えた場合には、コンパイラがそのことについてすぐに教えてくれるでしょう。 これによって多くの単純なミスが数秒で修正でき、プログラマーが自分でミスを見つけないといけないような言語と比べて全体の開発時間を大いに削減できるのです。

いやホントそうなんだよね、これが TypeScript & Redux だと never を使いましょうみたいにしかならない認識で、それでも結局実行時例外を拾いきれない事があったので、前に Elm をちょっと触った時「感触が良い」と思った部分の一つなんだよな・・・

上で定義したtoName関数はうまく動きますが、ageの値を実装内で使っていませんよね?使っていない関連データには名前を付ける代わりに「ワイルドカード」を使うのが普通です。 _はそこになんらかの値があることを示しつつも、実際にはその値が特に使われていないことを表現しています。

ははぁ、 Ruby なりでも使わない変数名なりの頭に _ つけると警告しないでくれるあの辺かな。 Elmの場合には別に使わなくてもコンパイルエラーにはならないような感じだけれども、 _ を prefix にしたりも出来るのかな?

-- UNEXPECTED NAME --------------------------------------------- Jump To Problem

Variable names cannot start with underscores like this:

8|     Regular name _age ->

                    ^^^^
You can either have an underscore like _ to ignore the value, or you can have a
name like age to use the matched value.

出来ないっぽい。ちょっと残念。

https://guide.elm-lang.jp/error_handling/

エラーハンドリング

Elmが保証してくれる安全性の1つに、ランタイムエラーを実際に見ることはないということがあります。その理由としては Elmはエラーをデータとして扱うということ が挙げられます。エラーが起こってアプリケーション全体がクラッシュするよりは失敗の可能性を明示的にカスタム型を使って表現するほうが好まれます。

お、気になるところではあるのだけれど、 ランタイムエラーを実際に見ることはない なのか。本当なら凄いな・・・

例えば、ユーザからの入力を年齢に変換したいとしましょう。カスタム型をこのように作りましょう:

type MaybeAge
  = Age Int
  | InvalidInput

toAge : String -> MaybeAge
toAge userInput =
  ...

-- toAge "24" == Age 24
-- toAge "99" == Age 99
-- toAge "ZZ" == InvalidInput

でたー! Maybe! Haskell 入門しようとした時に引っかかった部分の一つだった気がする・・・ ・・・が、まぁ、ここまでは別に理解できるか。言語機能というか組み込みとしての Maybe 的な話じゃなくて、単なるカスタム型での表現の話だもんな。

toAge関数にどのような入力が与えられた場合でも常に値を生成します。有効な入力の場合はAge 24やAge 99などの値を生成するのに対して、無効な入力の場合はInvalidInputという値を生成します。それからパターンマッチを使って両方の可能性が確実に処理されるようにします。クラッシュはしません!

クラッシュはしません!

強い!頼もしい!

このような状況は極めて一般的です。その場その場に合わせてカスタム型を作成したほうがしばしば有益ですが、より単純な場合には代わりに既製の型を使用することができます。それでこの章の残りの部分ではMaybe型とResult型を新たに学び、この2つの型がどのようにエラーをデータとして扱うのを助けることができるかを示します!

おー、ここでいよいよ組み込みの? Maybe が出てくるわけか・・・

https://guide.elm-lang.jp/error_handling/maybe.html

Elmをよく書くようになるとMaybe型を非常に頻繁にみるようになります

まじで・・・?ちょっと引いてしまうんだが・・・

Maybeは以下のように定義されています:

type Maybe a
  = Just a
  | Nothing

-- Just 3.14 : Maybe Float
-- Just "hi" : Maybe String
-- Just True : Maybe Bool
-- Nothing   : Maybe a

シンプルそうに見えてよくわからんやつだ・・・

Maybeは2つのバリアントを持つ型です。つまり何も持っていない(=Nothing)か、ちょうど(=Just)1つの値を持っているか、です。Maybe aの型変数は具体的な値次第でMaybe FloatやMaybe Stringといった型を持つことを可能にします。 Maybeには主に2つの使いみちがあります。部分関数と入力が任意のフィールドで役立ちます。

わからんが、ここで深堀りすると何も実用的なものを得られないまま終わっちゃう可能性が高いので、後から理解できる==未来の自分に期待して一旦先へ進む

ある入力に対しては答えを与えるが他には与えない関数が欲しい場合があります。多くの人がそういう関数に遭遇するのは、ユーザからの入力を数値に変換しようとしてString.toFloat関数を使おうとしたときでしょう。

ん、String.toInt も調べてみたら同じく Maybe を返すっぽいが、これ特に意識せず使わなかったっけ? と思ったら、あれは String.fromInt だった。そっか、String.fromInt が失敗する事はないというか型チェックで全部済むけど、 String.toInt なりはそうもいかないという事ね・・・それはそう。

文字列をfloatに変換できますか?多分(=Maybe)!変換したら、そのデータをパターンマッチして適切に処理を続けましょう。

うむ。。。謎である。

演習: 摂氏から華氏に変換する小さなプログラムをここに書きました。さまざまな方法でviewコードをリファクタリングしてみてください。無効な入力の周りに赤い枠を付けることはできますか?他の変換を追加することができますか。華氏から摂氏?インチからメートル?

さて、ここで紹介されているリンクが https://ellie-app.com/bJSMQz9tydqa1 とか言うところに飛ばされて、なんらかの accept を求められて怪しい気がしたのでちょっとストップしてしまった。本家ガイドでもそうなのか?と比較してみたが同じリンクだった。ならいっか、と accept したら普通にオンラインエディタ系の画面が開く。これはこれで良いんだけど、他と違うのなんでだ?という疑問が・・・

ここでちょっと休憩がてら、昔買って読んですべて忘れてた 基礎からわかる Elm を開いてみる・・・と、イキナリ答えが出てきた。こいつは Elm 作者の同僚が作ったサイトらしく、WebAssembly で作られているんだと。ほうほうーと、そしてここでREPLについての答えも出てきた。本家のインストールすれば elm repl で開けるらしい。 ガイドだと、もうちょい先の章にインストールの説明がある https://guide.elm-lang.jp/install/elm.html これは、どういう流れで読者に読んでほしいかという思想の違いだと思うのだけれど、結果として並行読みに助けられる感じになった。 この手の言語はそうだろうなぁと思いつつ、REPLでは複数行への対応に \ を都度入れなきゃいけないのが面倒くさそうではあるが、やはりCLIから入力できるのは楽なので適宜使っていきたい 書籍の序盤を読んで感心したことのもう一つが「Elm はversion upに伴ってどんどん破壊的変更を入れてきたが、それはむしろ機能を削ぎ落としてシンプルさを追求してきた結果」というような事が書かれていてすげーなと思った。確かにいくつかのモジュールのリファレンス見てもあまり量が無かった。

もう一つ脱線で、ぐぐったときに https://www.kabuku.co.jp/developers/the-case-for-mint を見つけた。 kabuku さんの事はあまり良く知らないのだけれど、 https://www.kabuku.co.jp/developers/good-bye-typescript-enum 等にはお世話になったし、フロントエンドに強いところなのだろうと思っていてちょっとこの Elm 評価には怖くなってしまったが、一旦あまり気にしないことにする・・・

で、 https://ellie-app.com/bJSMQz9tydqa1 に戻る。まず目を引いたのが html で

<html>
<head>
  <style>
    /* you can style your program here */
  </style>
</head>
<body>
  <main></main>
  <script>
    var app = Elm.Main.init({ node: document.querySelector('main') })
    // you can use ports and stuff here
  </script>
</body>
</html>

自分はどちらかと言うとSPA方面の技術力向上をそんなに狙っていないというか「自分には難しすぎる気がするので、個人プロジェクトではなるべく避けたい」と思っている方なので、ページ単位で別のコード使えるんなら良いなーと思ってたんだけど、それは Elm 的にどうなんだろ。 Routing を本当に必要かどうかはよく考えろみたいなのは見たけど、それはそのままSPA否定ということにはならんと思うし・・・まぁいいや、おいとく

演習: 摂氏から華氏に変換する小さなプログラムをここに書きました。さまざまな方法でviewコードをリファクタリングしてみてください。無効な入力の周りに赤い枠を付けることはできますか?他の変換を追加することができますか。華氏から摂氏?インチからメートル?

これはリファクタリングでは無くてエンハンスでは!?とちょっと思ったが置いとく。

無効な入力の周りに赤い枠を付けることはできますか?

いやー、いかにも簡単そうなんだけど

viewConverter : String -> String -> String -> Html Msg
viewConverter userInput color equivalentTemp =
  span []
    [ input [ value userInput, onInput Change, style "width" "40px" ] []
    , text "°C = "
    , span [ style "color" color ] [ text equivalentTemp ]
    , text "°F"
    ]

この style "width" "40px"、よく見るとというか、癖強すぎでは・・・? しかしこういうときこそ Elm のユーザーフレンドリーなところで、 オンラインエディタから常にリファレンスへ簡単に飛べるようになっているのだ。

https://package.elm-lang.org/packages/elm/html/1.0.0/Html-Attributes#style

style : String -> String -> Attribute msg

このシグネチャ?は、まぁなんとなくそんな感じだろうなと思っていた。

greeting : Node msg
greeting =
  div
    [ style "background-color" "red"
    , style "height" "90px"
    , style "width" "100%"
    ]
    [ text "Hello!"
    ]

なるほどー?複数 style を指定したいときは戻り値 の list にしろということなのかな。ま、なんとなく想像してたけどね!

There is no Html.Styles module because best practices for working with HTML suggest that this should primarily be specified in CSS files. So the general recommendation is to use this function lightly.

基本使うな、CSSファイルなり使えと。まぁそらそうよねという気はする。

複数 style を指定したいときは戻り値 の list にしろということ

これを確認するには、むしろこの戻り値を受け取ってる側の定義を確認する必要があるなと思って見てみた

https://package.elm-lang.org/packages/elm/html/1.0.0/Html#input

input : List (Attribute msg) -> List (Html msg) -> Html msg

そうっぽい。可変長引数とかありなん?と思ってたけど、そもそも順番気にしないと言うか、Attribute msg をいくつでも受け取ってうまいことさばいてくれる。と理解した。便利では?

というとこで今度はフロントエンドという大枠以上に苦手意識のあるCSSの話になった

CSS input 枠線 でぐぐると怪しげな日本語サイトが上位に来てしまうので、 CSS input border MDN で探す。なんでもMDNに当たっとけばいいっしょぐらいの思考である。

https://developer.mozilla.org/ja/docs/Web/CSS/border https://developer.mozilla.org/ja/docs/Web/CSS/border-color

まずは面倒くさい条件分岐のコード考える前に、本当にそれで色が変わるのか試してみる

input [ value userInput, onInput Change, style "width" "40px", style "border-corolr" "red" ] []

・・・変わらない。というか、今気づいたけど青い。 style "border-corolr2" "red" みたいに存在しない?プロパティ名を指定してみたがコンパイルは通ってしまう。なにー、コンパイルエラーにならんのかー!と一瞬思ったが、型定義的に正しいし、そもそもそれは別にこのレイヤーで確認するべき話と言うことではないのかなと思ったのでぐっとこらえる。

何故青いのか?は、よくコードを見ると別のところで色指定、それもまさに blue がされているのでこれっぽいな・・・

view : Model -> Html Msg
view model =
  case String.toFloat model.input of
    Just celsius ->
      viewConverter model.input "blue" (String.fromFloat (celsius * 1.8 + 32))

    Nothing ->
      viewConverter model.input "red" "???"

viewConverter : String -> String -> String -> Html Msg
viewConverter userInput color equivalentTemp =
  span []
    [ input [ value userInput, onInput Change, style "width" "40px"] []
    , text "°C = "
    , span [ style "color" color ] [ text equivalentTemp ]
    , text "°F"
    ]

いや、それは算出した温度に対しての色変化っぽいから、枠線の色とは関係ない?そもそも枠線ってデフォルト青だっけ?とか混乱してきた。 とりあえず blue を black に変えると、枠線は青いままで温度のところが黒くなった。つまりそういうことだよなぁ・・・ え、じゃあ枠線はどうするんだ?

[ input [ value userInput, onInput Change, style "border-corolr" "red", style "width" "40px"]

順番変えても反映されないしな・・・ 渋々StackOverFlowとかもみてみたけど、inputにこのプロパティが使えないこと無いような・・・ というところで気づいた。typo だこれ。

input [ value userInput, onInput Change, style "width" "40px", style "border-color" "red"] []

赤くなった。 むむむ・・・これは、どうなのだろう?一度は 別にこのレイヤーで確認するべき話と言うことではないのかなと思った 話なのだけれど、 React & TypeScript の style だと防げた話だと思うんだよな・・・

https://ja.reactjs.org/docs/dom-elements.html#style

ふむー

    このドキュメンテーションにあるいくつかの例では style を便宜上使用していますが、style 属性を要素のスタイリングの主要な手段として使うことは一般的に推奨されません。多くの場合、className を使って外部の CSS スタイルシートに定義された CSS クラスを参照するべきです。React アプリケーションの中では、style は動的に計算されたスタイルをレンダー中に追加するために最もよく使われます。FAQ: Styling and CSS も参照してください。

いやーそうなんだよね、普通は外部のCSS使いましょうはわかりつつ、動的に計算されたスタイルを入れる的な話では使う機能なわけで、その時にここに type check が効かないのは単純に不便では? と思った。が、まぁ考えようによっては「CSS当たらなくても最低限使えるように作っとけ」とも言えるかなと思ったので、ここは自分の中で「要注意だな」とだけ覚えておいて、良否判断はしないでおく。

さて本題の条件分岐だけど「この条件のときだけ可変長引数をこうしたい」は Ruby だと簡単だと思うけれど、Elmではまだ習ってないなというか、なんか筋悪な気がする。というところでコードをちょっと広く見てみると、そもそも色渡しを既に外側でやっているのだからそれに乗っかれば良いのだと気づいた。

view : Model -> Html Msg
view model =
  case String.toFloat model.input of
    Just celsius ->
      viewConverter model.input "blue" (String.fromFloat (celsius * 1.8 + 32))

    Nothing ->
      viewConverter model.input "red" "???"

viewConverter : String -> String -> String -> Html Msg
viewConverter userInput color equivalentTemp =
  span []
    [ input [ value userInput, onInput Change, style "width" "40px", style "border-color" color] []
    , text "°C = "
    , span [ style "color" color ] [ text equivalentTemp ]
    , text "°F"
    ]

なんのことは無い、これだけで済む話だったのだ。しかしここに辿り着くまでにかなり時間を食った。40分ぐらい?

他の変換を追加することができますか。華氏から摂氏?インチからメートル?

めんどくさ・・・と思ったが、一応やるかー?というか、この辺も確か数年前に通った道のはずなんだよな・・・と思ったけど

screen_shot 2021-06-26 20 11 56

そういえばガイドの上部にずっとこんなのが出ていた。ということは、これも当時はちょっと違ったかも?でも摂氏華氏辺りはやった気もするんだよなー。ただ日本で暮らしていると「華氏もインチも縁がない」ので、そこを調べるところからしてやる気が出ない。

とりあえず 追加 というのが厄介だなと思った。丸々機能書き換えなら input といったフィールド名もそのままでいけるだろうから楽だと思うんだけど、追加となると Model の定義からいじるわけだよね・・・こうなってくると、そろそろオンラインエディタというよりは慣れ親しんだ vscode を使いたいところだ

さて、自分の vscode には昔入れた elm の機能が入っている・・・のだけれど、よく見ると deprecated になっている

https://github.com/elm-tooling/vscode-elm-old

screen_shot 2021-06-26 20 31 13

今は https://github.com/elm-tooling/elm-language-client-vscode https://marketplace.visualstudio.com/items?itemName=Elmtooling.elm-ls-vscode を使えとのことなので、仰せのままに古い方をuninstallしてこっちを入れてみる。

で、適当に対象のコードを .elm というファイル名で保存して vscode で開いてみたが・・・ハイライトすら効かない ぐぐるとなんか他のアプリケーションで使われている拡張子みたいだから、避けてるのかな?と思ったが、 https://en.wikipedia.org/wiki/Elm_(programming_language) を見ると .elm になっている。 とりあえず https://github.com/elm-tooling/elm-language-client-vscode の README から、入っていないツールを入れに行く

Install elm-test and elm-format by running npm install -g elm-test elm-format in a terminal (Optional) Install elm-review via npm install -g elm-review in a terminal and enable it in your settings

変わらない・・・

Most features don't seem to work for me?

    This tool needs a valid elm project, please check if you have an elm.json. You can easily initialize your project with elm init. If it still does work, please try if you get the same behavior with the elm-spa-example.

あ、やっぱり(負け惜しみじゃないよ) elm.json が無いから?そんな気はしてたんだよねー。 こういうの作るの面倒くさそうだなと思ってたんだけど、 elm init だけで良いと言うならやってみるか でやってみると src というディレクトリも出来たのでここに突っ込んで見る・・・が、やはりハイライト効かないぞ?

https://guide.elm-lang.jp/install/editor.html を見てみると https://github.com/elm/editor-plugins のリンク集が紹介されていて、ここから飛べるのが https://github.com/elm-tooling/elm-language-client-vscode で、13日前に更新かかっている。やはりこいつが今の現役プラグインっぽいんだけれども。 なんか設定いるのかな?と思ってプラグインの説明をvscodeから開くと、なぜかinstallされていない・・・え、さっきしたはずなのに・・・?まぁここを深追いしてもしょうがないのでinstallしてみたらあっさりシンタックスハイライトが効いた。これは Elm 全く悪くないんだろうけどどっと疲れてしまった・・・

しかしこのプラグインかなり便利そうだぞ。 関数名にカーソル合わせるとちゃんと説明が出るし、定義元ジャンプも効く、F2での一斉renameも出来る・・・めっちゃ快適やん。Haskell の学習あまり気乗りしなかった理由がIDEの有力なやつが当時なさそうな事も大きかったんだけど、Elmで出来るんだったらHaskell でもできそうな気がするな・・・ と思ってググったら今は https://github.com/haskell/haskell-language-server とやらが有力らしい。というか自分 ⭐ つけてある。vscode にも対応していそうだし、今度もっかいチャレンジしてみるかー

というところで1日が終わった。

pankona commented 3 years ago

Elmに関しては 2年ぐらい前に @pankona さんとペアで4時間ぐらい?

さすがにもうちょっとやったような気がする…? 当時よりも我々にはフロントエンド力 💪 がついているはずなので、いまやったらもっと "理解る" かもしれない…!

kachick commented 3 years ago

@pankona6~8時間 ぐらい に書き換えておいた!

当時よりも我々にはフロントエンド力

フロントエンド力どうこうよりも、関数型言語のところで挫折した記憶があるからあんま関係無い気がするんだよなぁw フロントエンドに関しても、Reduxは苦手なままだし・・・ でも、小規模だからなのか、同じファイルの中に Redux 的な概念が小さく詰まっていると言うだけでも全然違うねー。今の所ずっと感触が良い。ただ、Reduxも非同期通信が混ざってきて redux-saga とか redux-thunk 使う辺りからなんだこれになってきたらからまだ油断は出来ない気がしている。

pankona commented 3 years ago

読んだ。グレート!

前にやったときは、枠を赤くするみたいのはやったような記憶があるね?手元にソース残ってるかな...?というかどっかにコミットしてたような...? https://github.com/pankona/elm-architecture-tutorial これだっけ...?

kachick commented 3 years ago

@pankona なにかのリポジトリをクローンしたりせずgudeだけに則ってやってた気がするから、それは自分で独習したんじゃない?w

とは言うものの、自分も謎のリポジトリを2年前に作っている

https://github.com/kachick/learn_Elm_todo => Todoを手習いにするつもりだったのに、もう作ってた・・・!? https://github.com/kachick/learn_Elm_command => CLIか?と思ったら、なんか外部のエンドポイント叩いている気配があるのでその練習っぽい

いやー、当時せっかくかけた時間がお互い無駄になって感が否めないですなw ちゃんと手に馴染ませないと・・・

kachick commented 3 years ago

2021-06-27

他の変換を追加することができますか。華氏から摂氏?インチからメートル?

重い腰を上げてやることにしたけれど、両方やる気力もわかないので インチ -> メートル変換だけ追加しようと思った。 換算定義はぐぐればすぐ出てくる https://kenkou888.com/category21/inch_m.html が・・・詰まった。 View の変更は簡単だ、なんとなく span をコピーして div で括って間に br を挟めばとりあえず動く。

import Html exposing (func)

の定義が、 vscode の quick fix から auto import した時に既存の定義へ追加してくれないのが不便だけれど、別にそれで動かないというわけでもないみたいなのでいっかと思った。

model への フィールド追加も別に迷うことはない

動作確認も、 elm reactor を使ってサクサクである。この辺りの環境構築の楽さも TypeScript + React + Redux 辺りと比べると段違いだ。一応コンパイルされた JavaScript のコードも軽く眺めてみたけれど、まぁ所謂 AltJs とは毛色が違うのはそういうもんだろうなーと思った。

問題は update の部分だ。 2つのフィールドに対して両方が maybe の時にうまいことパターンマッチを書く方法があるのかよくわからない。 何も考えずにやるなら 1つのパターンマッチの中に次のパターンマッチをネストして・・・とやればなんとかなるだろうけど、さすがにしんどいしそんなの嫌だ。 しかしそうすると、ここはどう表現すれば良いんだ・・・? 基本的な話だという気がしていてここは放置しちゃいけない部分だろうなとは思いつつ、かなり解決に時間を食いそうな予感がするので一旦飛ばしてしまう。多分このまま進むとどっかで詰まるだろうから、そこで改めて対処したい

使いすぎを避ける

nullable に対してパターンマッチを強制するので堅いが、それよりもそもそも型エリアスを駆使したほうが良いですよ。という理解をした。忘れないようにしたい。これは TypeScript でも使える考え方なのではないか?

bad

type alias Friend =
  { name : String
  , age : Maybe Int
  , height : Maybe Float
  , weight : Maybe Float
  }

good

type Friend
  = Less String
  | More String Info

type alias Info =
  { age : Int
  , height : Float
  , weight : Float
  }

https://guide.elm-lang.jp/error_handling/result.html

Result

type Result error value
  = Ok value
  | Err error

なんとなくこういう型エリアスなんだろうなーというイメージは掴めていると思うのだけれど自信はない。 バリデーションエラーの表示分けが例に使われている。

次の章では実際にHTTPリクエストの作り方を見せていきます。なのですぐさまResult型とError型に再会することになりますよ!

お、大事なところへ入っていくなー。なんかやっぱそろそろ詰まりそうだけれど、サーバー通信介さないTODOとかだったらここまでの知識量で一応作れそうな気がするし、一旦 guide から離れるのも有りかもしれない。

https://guide.elm-lang.jp/effects/

Browser.sandbox

そうなんだよねー、ここが結構パターンがあって、例によく使われる Browser.sandbox はシンプルコード用で複雑になってくると別のを選ばないと行けないような理解は2年前にもしてた気がする。

続くいくつかの例ではプログラムを作るためにBrowser.elementを使っていきます。そこでは外の世界とのやり取りを可能とするためのコマンドとサブスクリプションの概念を導入していきます。

なるほどー。SPAとかだと最低限これは必要になりそうだな。

このプログラムではHtmlという値に加えて、CmdとSubなる値をランタイムシステムに送り出します。ここでは我々のプログラムがランタイムシステムに対してHTTPリクエストを送ったり乱数を生成するよう指示することができます。また現在時刻をランタイムシステムから待ち受けて我々のプログラムで使うこともできます。 いくつかの例を見ていくと、コマンドとサブスクリプションについての理解が深まると思いますので、やってみましょう!

Note 3:「指示する」を意味する英語のCommandがCmdの語源です。英語のSubscriptionがSubの語源で、主に定期購読と訳されますが、Elmでのイメージは外部から来るメッセージを定期的に待ち受けるところからから来ています 。

あー、 https://github.com/kachick/learn_Elm_command の 「command」 はこれを指してたっぽいな。何も覚えてないけども、この辺りまでは2年前にも進めてた部分という事だと思う・・・

kachick commented 3 years ago

https://github.com/kachick/times_kachick/issues/143 が終わったので、この issue の goal を Todo から https://github.com/mobu-of-the-world/mobu の少機能クローンを作るところにしようと思った。(https://github.com/kachick/times_kachick/issues/86 へ書いたやつ)

が・・・ List の shuffle で詰んだ。Ruby だったら Array#shuffle 一発なやつだが、どうやら elm だと core から今は Random モジュールが切り離されていて、尚且そいつが Generator とか言うのを返すようになっているらしく単純置換ではいかない・・・ え、こんなところでこんな難しそうな事させられるの!?と思ったが、これは必要な「マインドセット シフト」らしい。敢えて注釈がついているということは、ここで引っかかる自分のような入門者がかなり多いのだろう

https://package.elm-lang.org/packages/elm-community/random-extra/latest/Random.List https://package.elm-lang.org/packages/elm/random/latest/Random#generate https://guide.elm-lang.jp/effects/random.html https://github.com/elm-community/random-extra/blob/d52055975644ad401709c2aff14dab9ca93e44a0/tests/Tests/Random/List.elm#L13-L24 https://package.elm-lang.org/packages/elm/random/latest/

Note: Read through guide.elm-lang.org to learn how commands work. If you are coming from JS it can be hopelessly frustrating if you just try to wing it. And definitely ask around on Slack if you feel stuck! Investing in understanding generators is really worth it, and once it clicks, folks often dread going back to Math.random() in JavaScript.

Mindset Shift

If you are coming from JavaScript, this package is usually quite surprising at first. Why not just call Math.random() and get random floats whenever you want? Well, all Elm functions have this “same input, same output” guarantee. That is part of what makes Elm so reliable and easy to test! But if we could generate random values anytime we want, we would have to throw that guarantee out.

So instead, we create a Generator and hand it to the Elm runtime system to do the dirty work of generating values. We get to keep our guarantees and we get random values. Great! And once people become familiar with generators, they often report that it is easier than the traditional imperative APIs for most cases. For example, jump to the docs for Random.map4 for an example of generating random quadtrees and think about what it would look like to do that in JavaScript!

Point is, this library takes some learning, but we really think it is worth it. So hang in there, and do not hesitate to ask for help on Slack or Discourse!
kachick commented 3 years ago

なんとか乱数パートを乗り換えた。完全に理解したとは言い難いけど、 https://qiita.com/ababup1192/items/47ebac90daf3d7a26f56 を読んで、なるほど https://github.com/kachick/learn_Elm_command でやった Cmd やなという事でこれを書き換えていった。 他に学んだこととしては https://qiita.com/mather314/items/1d5917b2b9f415d18372パイプライン演算子(Pipeline Operator) ・・・と言いたいが全く分かっていなくて読み飛ばした。 後は無名関数の書き方 https://sporto.gitbooks.io/elm-tutorial/content/jp/01-foundations/02-functions.html と、 List.filter https://package.elm-lang.org/packages/elm/core/latest/List#filter

kachick commented 3 years ago

次に localStorage で四苦八苦した。TypeScript の時は https://github.com/mobu-of-the-world/mobu/pull/20 みたいに cookie を使った。自分が望んだというより @pankona さんの要求どおりにした機能だけど、なるほどこれが無いとリロードの度にデータが吹っ飛んで、利用というよりも開発がしづらい 😅 で、今回も同様にしようかなと思って https://github.com/elm-lang/cookie を見に行ったら

Cookies are an ancient mistake of web browsers. They make a bunch of security problems quite easy, so consider this an expert library.

If you are looking for a way to store information, you should take a look into local-storage or session-storage instead.

はい、すみません・・・ よく違い分かってないんだよねということで調べてみたら、session-storage は揮発性が高くて local-storage は割と長期保存向きという雑な解釈。 で、 elm で localStorage の使い方を調べてみると・・・でた、いきなり port! なるほど、これが https://www.kabuku.co.jp/developers/the-case-for-mint でも言われていたような「基本的なことまでport使わされる」ってやつか・・・?確かにここで必須になるのは辛いものがあるなぁ。 https://github.com/billstclair/elm-localstorage を見たけどアクティブな更新されてないようだしなんかベタ書きの方がマシそうな気配があったので、取り敢えず下記の資料あたりを見様見真似でやってみた。

https://guide.elm-lang.jp/interop/ports.html からまず参照されている https://github.com/elm-community/js-integration-examples/tree/master/localStorage へ行き、関連コードをほぼそのまま使わせてもらった。E とか D で import するの気持ち悪くないのでそのままにして https://package.elm-lang.org/packages/elm/json/1.1.3/ を読みながら・・・ しかし decoder の定義がよくわからない。 Json.Decode.map2 とやらは 2 filed のときなんでしょ?だから Json.Decode.map で十分では・・・と差し替えてみるもコンパイルが通らない。また map2 でも 1引数目を list の decoder にするとダメだけど、順番変えると通るみたいなかなり謎な状況になってしまった。しかしまぁ全部 storage に突っ込んじゃえば良いやということでとりあえずそのままにしたらコンパイルは通った・・・が、実際には localStorage に入らない。 ここで https://guide.elm-lang.jp/interop/ports.html を読み直すと、より上の階層として https://guide.elm-lang.jp/interop/ があった。なるほど、index.html へのダイレクトなコンパイルは諦めて main.js にコンパイルし、index.html 側に ports の JavaScript 側コードを書けと・・・なるほど確かに動いた。動いたけど、これって elm reactor 使えなくなるよね・・・? なんか、かなり基本的なところから「嬉しさが目減りしていく」感があってちょっと辛い・・・が、まぁ、取り敢えず動いたので良しとするかー。 https://kachick.github.io/mobu-elm/ へ auto deploy するようにした。 repository は https://github.com/kachick/mobu-elm commit は https://github.com/kachick/mobu-elm/commit/f6f2ff5a01cab18cee183b2eb84f3f308a4ec832

kachick commented 3 years ago

index.html へのダイレクトなコンパイルは諦めて main.js にコンパイルし、index.html 側に ports の JavaScript 側コードを書けと・・・なるほど確かに動いた。動いたけど、これって elm reactor 使えなくなるよね・・・? なんか、かなり基本的なところから「嬉しさが目減りしていく」感があってちょっと辛い

とりあえず elm-live という外部ツールを使えば解消した。 起動のさせ方というかパスの扱いを間違えると

internal/fs/utils.js:620
    throw new ERR_INVALID_ARG_TYPE(propName, ['string', 'Buffer', 'URL'], path);                                                              ^

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string or an instance of Buffer or URL. Received undefined

とかいうエラーになる

elm-live src/Main.elm --open --start-page=docs/index.html -- --output=docs/main.js これは起動はするが、main.jsの相対pathの扱いが間違ってしまっていて実際には動かない

ので、ここに動いたやり方をメモる

$ npm install --global elm elm-live@next
$ cd ./docs
$ elm-live ../src/Main.elm --open -- --output=main.js

elm-live:
  Server has been started! Server details below:
    - Website URL: http://localhost:8000
    - Serving files from: /Users/kachick/repos/mobu-elm/docs

elm-live:
  The build has succeeded.

elm-live:
  Watching the following files:
    - **/*.elm
kachick commented 3 years ago

elm-live, --hot つけても hot reload 効かなかった・・・こうなると手動で make した方が楽という自体に。辛い。

その後割と集中して色々やったから、逆にログに何を残せばいいか思い出せないところが多いのだけれど、commit log みながら思い出して書く

https://github.com/kachick/mobu-elm/commit/c06307ab83ac6ad0bbd460b62d38ebd0f8365356 でかなり基本部分の実装を終えた。

Json.Decode.map2 とやらは 2 filed のときなんでしょ?だから Json.Decode.map で十分では・・・と差し替えてみるもコンパイルが通らない。また map2 でも 1引数目を list の decoder にするとダメだけど、順番変えると通るみたいなかなり謎な状況になってしまった

これを map に直せた。が、詳しい理屈を知るのは更に後

timer に関しては https://guide.elm-lang.jp/effects/time.html の subscription を見様見真似で使ったらうまく行った。結局時間を持つと機能的に扱いづらかったので、カウンターを持つ感じにしちゃったけど

let~in, Maybe, List.Extra.swapAt 辺りもここで初めて使ったかなー。Maybeは初めてじゃないけど、頭を使ったのは初めてというか・・・

https://github.com/kachick/mobu-elm/commit/ec9edd7d382cda4a7626fb3d1d8505e113dc7091 をやるために、 https://github.com/kachick/mobu-elm/commit/4ec70f59a64ae22ed4f28db7fb3882c9798ffbd7 で username だけじゃなく User というカスタム型?で表現するようにした。リファクタリングに安心感があるというのはそれはそうなんだけど、それでもやっぱ大変だったので、この手のデータは最初からちょっと拡張性持たせといた方が良いかも・・・ ここで改めて JSON の decode/encode に向き合う必要が出てきたのだけれど、 https://qiita.com/canisterism/items/3faafe384fb0d5c95708 がめっちゃ参考になった。これが無かったら挫折してたかもしれない・・・分かってみれば簡単というか気持ちがいい感じだ。これも所謂関数合成?

onError は Task だのなんだの求めてきて面倒だったので、 on "error" というのを使うことにした。違いはよく分かってない。 https://www.debugcn.com/ja/article/172783617.html の怪しい翻訳サイトっぽいのが役立った。

https://github.com/kachick/mobu-elm/commit/6c698fe1e409cfa634273ecbb7e7207a611c031e で input tag に number やら min, max といった attribute の渡し方を学んだ。 https://stackoverflow.com/questions/33857602/how-to-implement-a-slider-in-elm が役立った。

kachick commented 3 years ago

見た目というかCSS以外の機能で残ってるのは https://github.com/mobu-of-the-world/mobu/pull/79https://github.com/mobu-of-the-world/mobu/pull/13 かな? 手間掛かりそうならここで一旦 done でも良い気はしてきたが・・・

kachick commented 3 years ago

JSON の decode/encode 周りがかなり手探り感あったことと、コンパイル通ればOKと言い切れない部分だったので unit test の書き方を学ぶ良い題材だろうとしてやってみた

結果 => https://github.com/kachick/mobu-elm/commit/f76b03cc5c56dc4dac6f11f8f3a918f945dfba6f

まず https://github.com/elm-explorations/test を npm 経由で入れる必要がある。やたら ⭐ が少ない気はするけど test 系ツールが不遇なのはどこも一緒なのだろう。 test の書き方としては https://elmprogramming.com/easy-to-test.htmlhttps://becoming-functional.com/testing-json-decoders-in-elm-39f613a98895 が参考になった。 GitHub Actions でのCIやり方としては https://stackoverflow.com/questions/60058556/using-elm-test-in-a-git-hub-action が役立った。ただ、container 上だと package-lock.json に差分が出てしまうようなのがちょっと気持ち悪かった。 1 file で済んでた気持ちよさみたいなのがモジュール分割する必要出てきてちょっと残念だったけど、型を中心にモジュールへ切り出すと良いような感じの事が書いてあったので取り敢えずやってみた https://guide.elm-lang.jp/webapps/modules.html

後は Maybe 使ってる所に Maybe.withDefault とかいう便利そうなのが使えるかな?と思ったけど微妙にフィットしなかったので次の機会に回す。 他、今List使ってるけど用途的には Array の方が正しいかもなというのと、elm の Array には swapAt が無いみたいなので追加してみてようかなというのと、パイプ演算子をもっと活用できるか調べてみようかなと思った。 Array と パイプ演算子 に関しては 基礎からわかるElm を読んで惹かれた感じ。

kachick commented 1 year ago

https://github.com/mobu-of-the-world/emobu

CSS 整えるところ面倒で放置してたんだけど、grid template でざざっと分割してCI周り整え直す等して組織へ移管した。 第一部完! https://github.com/mobu-of-the-world/mobu/discussions/775