肥大化するModelTreeとどう戦うか(が分かっていない)
だんだんelmのサンプルが大きくなってきました。Natigationも導入し、いわゆるSPA化してます。
ここで問題になってきたのが、「どう巨大なTreeを管理するか」
言語の難しさの壁を越えたら次は他言語でもあるあるな問題に遭遇しました。
続きを読む関数型よく知らない人のelm失敗編(2) - module
つい先ほど、モジュールの「階層化」について知りました…厳密にはやり方が分かったといったほうが的確かも。
そりゃ普通に考えれば当然のことなので、あまり役に立たない気はしますが備忘録かねてまとめておきます
続きを読む関数型よく知らない人のelm入門(5) - Jsonパース実践サンプル集
フロントエンドのコードを作成するとき、誰もが遭遇するであろう Jsonパース について今回はとりあげたいと思います。
これがなれるまでつらい。
Java等の言語が「Jsonオブジェクトから要素をとりだしていく」のに対し、elmでは「パースロジックを組み上げいく」というフローで実態が見えにくく、理解を困難にしています。
特に「途中経過をログ出力させる」のが難しい(面倒)。パースをミスしたときのエラー原因も分かりづらいです。
そこで今回はまずは組めるようになるという方針でいきます。
題して、JSONパース実践サンプル集
細かい理論は後回し、習うより慣れろ、理解はできた後にやってくるのコンセプトの元、とにかくサンプルコードを書いてみます。
ぜひオンラインエディタで色々編集しながら試してください。
続きを読む関数型よく知らない人のelm入門(3) - type alias詳解
毎日ちょっとずつではありますがelmを触ってます。
今回は type alias についてです。
結構クセが強くハマったので自分なりの理解をまとめていきます。
type alias
色んな言語で採用されている同様のものと同じように、 elm においても「型に別名を付ける」のが type alias です。
type alias UserId = Int getUserNameForId UserId -> Maybe String
こんな感じで使えば「目的」が明確になり、引数や戻り値として分かりやすくなる…といいたいところですが、 elm上では、 type alias で別名宣言しても元の型と同じとしてみなされます
コードを見た方が早いかと。次のような感じです。
intValue : Int intValue = 3 --普通にビルドが通り動く getUserForId intValue
こういう宣言をした場合、別の型とみなしてコンパイルを通さない言語もありますが、 elm は通します。 ここ次で大事なのでしっかり把握してください。elmは別名の型は元の型と区別しません。
よって書いておいて何ですが、上記のような使い方はビルドエラー検出としては使えません。(ただし関数の引数・戻り値に使うことで可読性はあがります)
構造体
そこで使われるのが 構造体 として使い方、JavaでいうならEntity-classとしての使い方です。複数のデータを一括して保持するもの、ですね。
type alias Student = { id : Int , name : String } myStudent : Student myStudent = Student 10 "Taro" --IDと名前を連結する joinData : Student -> String joinData s = (toString s.id) ++ ":" ++ s.name joinData myStudent -- "10:Taro"
こんな感じ。生成部分の表記が特殊ですが、ようは Student という Int と String を受け取る関数があるようなイメージですね。
ここまでは問題ないと思います。では次に同じ情報を保持する Teacher クラスを作ってみましょう。
--Student同様のデータを保持 type alias Teacher = {id: Int, name: String} myTeacher : Teacher myTeacher = Teacher 100 "Mike"
ここまではいいと思います。 では次のコードはどうなると思いますか?
--Studentのidとnameを連結する getStudentData: Student -> String getStudentData s = (toString s.id) ++ ":" ++ s.name --それにTeachを渡す getStudentDatas myTeacher
ビルドエラー?それとも値を出力する?
正解は「値を出力する」です。 100:Mike
と表示されます。
このへん、普通の言語の感覚だと苦しみます。だって型が違うのに受け付けて動いているのですから。まるで動的型付け言語のような挙動です。
そこで思いだしてほしいのが最初の UserIdの例
そこで書きましたとおり、type alias はあくまで別名で、元の型が一緒なら区別しない ということです。
つまり、 Student と Teacher は内部的には同一のものとして扱われます。なんとなくこの辺はJaavscriptっぽいですね。
なので、特に型を記述しなくても呼び出せます
getStudentData {id=5, name="Hoge"} --"5:Hoge"
ここ注意してください。
ただし、構造体の要素が違う場合はビルドが通りません。少ない場合や型が違う場合もダメですし、「要素が多い」場合もビルドエラーです。
--同じように id/nameはあるが一個多い type alias Master = {id: Int, name:String, level: Int} myMaster : Master myMaster = Master 99 "Yoda" 999 getStudentData myMaster -- ビルドエラー
javascriptだと問題なく動くパターンですが、elmではビルドを通しません。MasterとStudentでは、元の型構造が異なるためです。
よって「type aliasとして別々でも、同じ型として扱われる」ことで困ることはあまりないと思います。
値の更新
これに関係してくるのが、 構造体の値の更新 です。
elmでは以下のようにして値を更新します。i
student = Student 1 "Taro" student2 = {student | id = 2}
このようにすると、name は Taro のまま、 id のみ 2 になります。もちろんelmではこの辺の副作用がないので、 student 自体には影響を与えません。
java的にいうなら、「studentのcloneを作り、そのcloneの値を変更している」イメージです。
ここで問題。次のコードはビルドが通るでしょうか?
student = Student 1 "Taro" --id が Int でなくString student2 = {student | id = "2"}
正解は… ビルドが通ります。実行時もエラーになりません
実はこの場合、 Student ではなく別の新しい型、 {id:String, name:String}
が生成されて、student2はこの型になります。
よって次のコードはビルドが通りません。
getStudentData : Student -> String student = Student 1 "Taro" student2 = {student | id = "2"} getStudentData student2 -- ビルドエラー!
また、「元の構造体にない変数名」はビルドエラーです。
type alias Student = {id : Int, name : String} student = Student 1 "Taro" student2 = {student | level = 99} --levelがないのでビルドエラー
トリッキーな挙動ですね。代入する変数の型は違ってもいいのに、名前が違うのはダメという。
しかしこれが問題かというと、まず問題になりません。少なくとも自分はまだ経験していないです。
値更新時は意図しない型になりますが、その更新した値を使おうとするとどこかでビルドエラーになるからです。(上記の例でいう joinData)
なので普通問題にはならないと思います。たとえ型宣言部分を書き替えても、です。(どこかでビルドエラーになるはず)
子要素の更新
また厄介な点として、 構造体ツリーの子要素をダイレクトに更新できません 。以下のような本と出版社の情報を格納する構造体を考えてみましょう。
--出版社と書籍情報 type alias Publisher = { name : String, address : String} type alias Book = { title : String, publisher : Publisher} --インスタンス生成 book = Book "Secret" (Publisher "Kado" "Tokyo")
では出版社が引っ越したとします。book
を更新しましょう。(※もちろんelmでは値の更新はできないので、値を変更した別インスタンス生成になります)
--ビルドエラー。直接子要素は更新できない book2 ={book.publisher | address = "Osaka"} --このパターンもダメ book3 = { book | publisher = { book.publisher | address = "Osaka" } }
よって、このようなときは以下のように一度要素をバラすしかありません。
publisher1 = book.publisher publisher2 = { publisher1 | address = "Osaka" } book2 = { book | publisher = publisher2 } book2.publisher.address -- Osaka
一旦子要素自体を変数に格納し、それをベースに値を更新。さらにそれを使って Book を更新します。
この程度ならいいですが、階層が深くなると相当面倒でしょうね….
Modelの設計時に注意してください。
まとめ
注意点をまとめますと
- type alias で別名にしても、元の型が一緒ならelmは区別しない
- 値更新時、違う型をいれてしまうと別の型としてあつかわれる
- 一気に子要素を更新できない
この3点に注意しておけばまず困らないと思います。
クセが強いのは事実ですが、本当に学習が楽しい言語ですね。クライアントサイドがメインな都合で Haskell はイマイチ作りたいものがなく挫折したのですが、elmはフロントエンドを組めるので楽しいです。
また、ほとんどの場合でビルドさえ通れば問題なく動くのはちょっとした快感です(css等の表示ミスはありますが、ロジックはまず一発で動く)
フロントエンドの経験がある方前提ですが、Haskellのとっかかりとしてもオススメ。 Haskellほどエレガントではない ですが、理解しづらい部分がそぎ落とされているので入門的な意味では十分かと思います。