Elmでランタイムエラーを起こす方法を教えてもらった
※この記事の検証環境はElm 0.19.1です。
Elmでランタイムエラーを起こす方法をTwitterで尋ねたところ、すぐに教えてもらえました。Elmコミュニティすごい!ありがとうございます!!
attribute "" ""
— 業スーのラップ(みやも) (@miyamo_madoka) 2020年5月29日
確認
以下のコードを書いてみたところちゃんとエラーになりました。
コード
div [ attribute "" "" ] []
出てきたエラー
InvalidCharacterError: Failed to execute 'setAttribute' on 'Element': '' is not a valid attribute name.
エラーになった!やった!
なぜランタイムエラーを起こしたくなったのか
以上でこの記事終わりでいいんですが、どうしてランタイムエラーを起こしたくなったのかというのも書いておきます。
Elm製アプリケーションを運用していて、Sentryの導入を考えていました。で、JavaScript側にSentryをセットアップしたときに、Elm側で起きたエラーも拾ってくれるのだろうか...?という疑問がありました。おもむろにSentryをセットアップして、JavaScriptからエラーが飛ぶことを確認した後に、さてElm側でもエラーを起こして確認だと思ったものの、あれ、Elmってランタイムエラー起きないことを謳ってるよな。起こす方法あるの...?となりました。
なぜattribute関数でランタイムエラーを起こせたのか
これも蛇足ですが、どうしてランタイムエラーを起こせたのでしょうか。
エラーの理由は、エラー文に書いてあるとおりです。setAttributeを不正な引数で呼んだからですね。ただ、今日はせっかくの休日なのでソースコードを見てみたいと思います。https://github.com/elm/html/blob/master/src/Html.elm を見ると、DOMを吐き出す関数はこんな感じです。
node : String -> List (Attribute msg) -> List (Html msg) -> Html msg node = VirtualDom.node
なので、VirtualDom.nodeを見てみます。
node : String -> List (Attribute msg) -> List (Node msg) -> Node msg node tag = Elm.Kernel.VirtualDom.node (Elm.Kernel.VirtualDom.noScript tag)
なるほど...?
var _VirtualDom_nodeNS = F2(function(namespace, tag) { return F2(function(factList, kidList) { for (var kids = [], descendantsCount = 0; kidList.b; kidList = kidList.b) // WHILE_CONS { var kid = kidList.a; descendantsCount += (kid.__descendantsCount || 0); kids.push(kid); } descendantsCount += kids.length; return { $: __2_NODE, __tag: tag, __facts: _VirtualDom_organizeFacts(factList), __kids: kids, __namespace: namespace, __descendantsCount: descendantsCount }; }); });
なるほど!!(わからん)
深入りするとElmのVirtualDom実装理解という別テーマになってしまうので、完全理解は諦めて、このモジュールでsetAttributeしているところを見てみます。
function _VirtualDom_applyAttrs(domNode, attrs) { for (var key in attrs) { var value = attrs[key]; typeof value !== 'undefined' ? domNode.setAttribute(key, value) : domNode.removeAttribute(key); } }
ここだけ見れば難しくないです。domNodeはきっとvirtualdomがつくってくれた生DOMで、attrsはきっと {'class': 'hoge', 'id': 'fuga', ...}みたいな連想配列でしょう。ということは、Elmのdiv [ attribute "" "" ] []というコードを書いたとき、実行時には以下のようなことが起きているはずです。
domNode.setAttribute('', ''); // domNodeはdocument.createElement('div');相当のオブジェクト Uncaught DOMException: Failed to execute 'setAttribute' on 'Element': '' is not a valid attribute name. at <anonymous>:1:4
まとめ
ざっくり追って、どういうことかわかりました。VirtualDomに興味が出てきたので、時間があるときに今度はそちらを深追いしてみたいです。
プルダウンメニューを再帰的に表示する
プルダウンメニューの実装について。先日の記事でも触れましたが、あれについてもうちょっと書けそうなネタがあったので書いてみます。
つくったもの
こういうものです。

実装
このように使えるように書きました。
let filePulldown = { id = "File" , label = "File" , checked = False , children = PullDown.Children [ { id = "File/New" , label = "New" , children = PullDown.empty , checked = False } , { id = "File/Open" , label = "Open" , children = PullDown.empty , checked = False } , { id = "File/Save" , label = "Save" , children = PullDown.empty , checked = False } ] } in PullDown.view [ filePullDown ] Pulldown.rootLevel
入れ子構造になっていて、childrenに入れ続ければいくらでもネストした要素を表示できるようにしたかったです。
ネストした構造を再帰的に表示
view : List PullDown -> Int -> Html Msg view pullDowns level = let classes = classList [ ( "app-pulldown--level-" ++ String.fromInt level, True ) , ( "app-pulldown__children", level > 1 ) ] in ul [ classes ] <| List.map (viewPullDown level) pullDowns viewPullDown : Int -> PullDown -> Html Msg viewPullDown level pullDown = case pullDown.children of Children [] -> if pullDown.checked then li [ classList [ ( "app-pulldown--checked", pullDown.checked ) ] ] [ text pullDown.label , span [ class "material-icons", Html.Attributes.style "font-size" "16px" ] [ text "check" ] ] else li [ classList [ ( "app-pulldown--checked", pullDown.checked ) ], onClick (OnClick pullDown.id) ] [ text pullDown.label ] Children children -> if level > 1 then li [] [ div [ class "app-pulldown__label" ] [ text pullDown.label ] , span [ class "material-icons" ] [ text "arrow_right" ] , view children (level + 1) ] else li [] [ div [ class "app-pulldown__label" ] [ text pullDown.label ] , view children (level + 1) ]
viewPullDown関数が何をしてるかというと、パターンマッチでchildrenを見て以下の処理にしています。
- 空配列のとき:テキスト要素を返す
- 配列に中身があるとき:childrenを渡してviewを呼び出す
雑感
実務ではこういう深さが決まってないネストしたものを末端まで処理していくようなやつを書く機会に遭遇してなかった(あってもライブラリを使うとかしてた)ので、楽しかったです。
Html.mapでモジュール間のMsgの受け渡しをする
趣味でElm製Markdownエディタを開発しているのですが、ようやくふつうにファイルを開いてふつうに保存することができる感じになってきました。楽しいです。今日はmacOSのナビゲーションを真似して、画面上部にナビゲーションを実装してみました。

今回はこの実装のときに出てきた悩みと解決法について書きます。
モジュールに切り出すときに出てきた悩み
Main.elmはだいぶ大きくなってしまっていたので、モジュールを分けて、こんな感じにしようと思って実装をはじめました。
-- Main.elm view = -- いろいろ省略 nav [] [ -- プルダウンのデータを配列で渡したらいい感じに表示されてほしい PullDown.view [ filePullDown, viewPullDown ] ]
で、なんやかんやしてとりあえず表示するだけのところまではできたのですが、ある悩みが出てきました。
「あれ、Msgってどうやってやり取りすればいいんだろう」
やりたいのは、PullDownモジュールのなかで起きたことを、Mainモジュールのほうでさばくことです。さっきのGIFを見るとわかりやすいのですが、

ナビゲーション内の要素をクリックすると、画面の見た目が変わっています。つまり、「PullDownのView/Theme/Whiteをクリックした」というMsgをどうにかしてMainモジュールに伝えて、アプリケーションの状態を更新する必要があります。
Elm in Actionという本を以前買ってほとんど読まずに放置していたなと思い出し、中身を見たところ、答えが書いてありました。
Html.mapの型はこうなっています。
map : (a -> msg) -> Html a -> Html msg
Html.mapを使うと、Html a を Html bに変換することができます。今回、PullDown.viewの型はHtml PullDown.Msgですから、それをHtml Main.Msgに変換するということです。上記の関数の型にあてはめるとこういうことです。
map : (PullDow.Msg -> Main.Msg) -> Html PullDown.Msg -> Html Main.Msg
なので、このように利用します。
-- Main.elm type Msg -- = ... 省略 -- Main.Msgに、PullDow.Msg -> Main.Msg となるバリアントを用意 | GotPullDownMsg PullDown.Msg Html.map GotPullDownMsg (PullDonw.view ...)
これでできました。なるほど1。
雑感
ReactとReduxを使った場合はコールバックを渡してこのようになるかと思います。
const Main = (props) => { const handleClickPullDown = () => { props.dispatchPullDownClickAction(); } return ( <PullDown pullDowns={[ filePullDown, viewPullDown ]} onClick={handleClickPullDown} > ) } connect(mapStateToProps, mapDispatchToProps)(Main)
Elmの場合はonClickにMsgを指定するのでupdate関数でそのMsgの処理を行うことになりますが、Reactの場合はコールバックを指定するため、そこでいろいろな処理を挟むことも可能です。どこで何をするという処理の流れがElmのほうが厳密で、React/Reduxのほうが自由な印象を受けました。
また、いま自分がつくってるものが小さいアプリケーションなので考える必要がないだけで、もっと大きめのものをつくるとなったら、Modelやupdateの分割方針も考えないといけないのかなという感じがあります。Elm in Actionにはそのようなことが書いてあるのでありがたいです。今後も参考にしていきたいと思います。
-
だいぶ端折ってしまったので、気になる方はNekobitoの実装を覗いてみてください。↩
Custom TypesのデータをJS界に受け渡しする(Elm)
Elmでアプリケーションの状態をlocalStorageに保存しとくようなものを作っていて、カスタムタイプはどうするのか知らなかったのでメモ。
雑にそのままportに渡そうとしたところ、以下のコンパイルエラーが出てきました。
The
setStorageport is trying to transmit aColorThemevalue:259| port setStorage : ModelExposedToStorage -> Cmd msg
I cannot handle that. The types that CAN flow in and out of Elm include:
Ints, Floats, Bools, Strings, Maybes, Lists, Arrays, tuples, records, and JSON values.
Since JSON values can flow through, you can use JSON encoders and decoders to allow other types through as well. More advanced users often just do everything with encoders and decoders for more control and better errors.
上の ColorThemeというのが、localStorageに保存したかったけどできなかったカスタムタイプのデータですね。具体的にはこういうものです。
type ColorTheme = WhiteTheme | DarkTheme
で、エラーに出ている通り、
Since JSON values can flow through, you can use JSON encoders and decoders to allow other types through as well. More advanced users often just do everything with encoders and decoders for more control and better errors.
JSON encodersとdecodersとやらを使えばいいようです。
対応
どういうこと?
localStorageに保存するときにJSONの形にencodeして、localStorageから取り出してきたときにはElmのデータにdecodeしてます。問題になったColorThemeのところは、こんな感じでcaseで文字列にしたやつをencodeしているだけです。
デスクトップPWA楽しい
趣味で https://github.com/gaaamii/nekobito というMarkdownエディタをElmで書いているんだけど、久しぶりに開いたら、Chromeアプリとしてインストールできるようになっていた。

ダウンロードされたものを見ると、 app_mode_loader という謎ファイルができているようだ。あとでこれどういうことなのか見てみたい。
どういう仕組みなのかよくわかっていないけどたぶん、以前PWAお試しようと思って雑に置いておいたmanifest.jsonのおかげのような気がする。
なにはともあれ、Elmで書いたものが、ネイティブアプリのように起動できて使える...!!楽しい!!!
趣味開発やっていきたさが高まった。
(追記)デスクトップPWAというらしい。楽しい。
「Elm Meetup in Summer」参加した
書くのが一日遅れましたが、Elm Meetup in Summer - connpassに参加してきました! 楽しかった〜!昨日ちゃんと感想書こうと思ったけど寝てしまったので、いまさらですが箇条書きで書いておきます。
感想
- 人がたくさんいてElmの勢いを感じた。
- Elm in Actionという良さそうな本を知ることができてよかった。
- Fringe81の泉さんの個人開発リポジトリを知れてよかった。Firebaseは自分も趣味開発で使いたいので参考になる。
- モジュール分割はみんな悩んでそう。
- モジュール分割の方法が、これだ!っていうのがわかれば大きなアプリでも安心して作れそう。
- プロダクトで採用してるという人が思ったよりいたけど、Elmはまだメジャーバージョンが0なのに採用できるのけっこう勇気あると思う。どんと来い、breaking change という感じなんだろうか(自分も0.18のときに採用したので人のことを言えない)。
- Elm普段から使ってる人は割と普通に便利に使ってて、Elm大好き!というよりは淡々とやっていってる感じがあってよかった。
- jinjorさんのスライドを見ればElmの歴史がわかるので時々見返したい。
基礎からわかるElmを読んだ感想
ついにきた pic.twitter.com/EjLz6dDspP
— gaaamii (@gaaamii) 2019年2月28日
1年半くらい前に注文したものの、Elmのバージョンが0.18->0.19に上がる都合とかで出版予定日が延びていたElm本がついに昨日、自宅に届いていた。感動した。
勉強になったところとか、感想を書いていく。
型の話
Elmといえば静的型付け言語であり、No Runtime Errorなウェブアプリを書けるという良さがある。 一方で、型が読めないと何が何だかわからない。型の話がわからないと、自分で型を書けないのはもちろん、なんといってもドキュメントが読めなくて困る。
この本では、50〜71ページのおよそ20ページでこのElmの型についての説明が書かれている。この辺の節を読むと、制約つきの型変数、type aliasがコンストラクタを生成すること、カスタム型とパターンマッチなどについて学べる。とても丁寧でわかりやすい。特にtype aliasとかカスタム型は自分で勉強したときよくわからなくてつらかった思い出があるので、もっと早くこの本が欲しかった。
MaybeとかResultとか
Elmにはnullとかnilってものはないという話で、その代わりMaybeって型がある。Maybeはモジュールにもなっていて、withDefaultとかmapとかandThenなどの関数が生えているのでだいたいやりたいことはできる。なんとなく自分の理解が怪しかったので復習がてら読んだ。
Resultも似たようなものだけど、Maybe以上に自分の理解が怪しかったのはおそらく、APIへリクエスト投げてレスポンス取得して云々みたいな処理を実装しようというときに初めてこのResultに出会ったからだと思う。ElmでHTTPリクエストを送る処理はHttpというモジュールでやる。そこでResultという型が出てくるのだけど、もちろんResult自体はそれ以外のところでも使えるし、Httpのためだけのものでもない。
本書では、
Resultは失敗するかもしれない結果を表すデータ構造です。
と簡潔に書かれていて、例示されているコードもHttpとかは関係のないものになっている。「ElmでAPIへリクエスト投げてデータ取得する実装をやろう」となって、「実装してみたもののResultってやつがなんだかよくわからない」となったときに本書のこの辺を読むとすっきりわかってよさそう。
描画の仕組みと高速化
Html.KeyedとHtml.lazyの話。
ReactでもkeyアトリビュートとReact.PureComponentがあり、これらと同じようなものだと思う。
ナビゲーション
onUrlRequestとonUrlChangeの説明がありがたい。
ナビゲーション周りの知識はSPAつくるときに必須なので、これが簡潔にまとめられているのは助かる。
感想
Elmを使って、実行時エラーが発生しないアプリを作れるのは魅力的だけど、新たに言語覚えるのはしんどい。 日本語の書籍にまとまったことで、言語覚えるしんどさが軽減されて、Elm人気もじわじわ高まっていくんじゃないかと思った。
elm/timeの使い方よくわからない問題
あるイベントが起きた時刻をサーバーに送信したい!というときに、Elmではどう書くのか調べたけどよくわからなくて、頑張って実現するとこういう感じになってしまったというメモ。これでいいのかよくわからない。サンプルを書いたので識者の知見がほしい...。
Tine.every 1000 updateCurrentTime みたいなのを書いて、1秒ごとにmodel.currentTimeを更新するようにしてしまったけど本当にこれでいいのかわからない
— gaaamii (@gaaamii) 2018年11月12日
サンプル
疑問点
JSなら任意の時点で new Date()すれば現在時刻が取れるのだけど、Elmの場合はsubscriptionsに関数を入れて、一定間隔でmodelの値を更新して、使うときはその値を参照するみたいにしないといけないのか...?というところ。イベントが起きたときだけほしいのに、前もってそれをmodelに持って常に更新しておかないといけないのだとしたらなんか変な感じする。おれがやってることが間違ってるんじゃないか感がある。というのが現状。
Elmやってるけどどうか(途中経過)
趣味でElmを書いていて、最近では仕事でも一部Elmを使わせてもらっている。作っているもの自体は大きくないし、人に説明できるような理解度でもないのだけど、やっていってる途中でどう感じてるのか雑に書いてみてもいいのではと思ってだらだら書いてみた。
関数シグネチャの読み方
Elmでは関数の引数は一つしか取れない。そのため関数はこういう形になる。
append : List a -> List a -> List a Put two lists together. append [1,1,2] [3,5,8] == [1,1,2,3,5,8] append ['a','b'] ['c'] == ['a','b','c'] You can also use the (++) operator to append lists.
関数を利用しているappend [1,1,2] [3,5,8] の部分を見ると、なんか引数が2つあるように見えるけど、そうではない。append [1,1,2] を評価して返ってくる関数に [3,5,8] を渡している。
REPLで1つずつ試していける。
> List.append <function> : List a -> List a -> List a
List.appendは List a を受け取って List a を返す関数を返す関数だ。
> List.append [1,2,3] <function> : List number -> List number
List.append [1,2,3]は、List number を受け取って List number を返す関数だ。
> List.append [1,2,3] [4,5,6] [1,2,3,4,5,6] : List number
List.append [1,2,3] [4,5,6]は、List numberだ。
となる。ふむ。
謎の安心感
謎の安心感がある。型を合わせていけば何かが出来上がっていく感じがある。JS書いてると console.log とか多用してたけどElmではあんまり Debug.log してない気がする。型合わせていけばなんかちゃんとできるというのと、あとDebuggerという便利ツールがあって、状態の遷移がupdateを通る都度確認できるからというのもある。
Debugger
Debuggerはなんか普通に気に入った。Google Chromeの使いこなしテクニックとかいろいろ覚えるのしんどい。これくらいシンプルにアプリの状態見れるツールがあるのは嬉しい。

Elm Architecture
基本Model View Update の3つで、難しくないという印象。この印象のおかげで、Elmを始めた。
ただ、CommandとSubscriptionという副作用を扱うための登場人物がいて、これをちゃんと使えないとアプリケーション書けない。
Ports
JSとつなぐ仕組み。localStorage使いたいけどElm側から扱えないじゃんというような問題があって、そういうときはこの仕組みを使ってJSとつなぐ。
HTML見づらい問題
HTMLも関数で記述するので最初は見づらかったけどもう慣れた感がある。
モジュール分割
こうするべきというのがよくわかってない。コンポーネント分けについては以下のブログ記事を何度も読み返してる。
雑感
正直Elmを触りだしたのは半分冗談というか、社内LTネタになるかなくらいの気持ちだったんだけど、触り続けてるともうなんかJSとかTS書くよりこちらをやっていたほうが楽しいんじゃないかみたいな気持ちまで芽生えてきた。この謎の良さをもう少しちゃんと言語化できるようになるくらいまでは触り続けていきたい。
ElmでSPAの練習
NavigationとUrlParserのドキュメント見ながらこういうのを書いた。これでいいのだろうか。
Elm入門者向けハンズオン行ってきた
集合した様子
おつかれ様でした(・∀・)#elm_tokyo pic.twitter.com/bXOcbZGNWC
— afroscript@メルカリでエンジニアの組織づくり (@afroscript10) 2018年1月27日
(懇親会後の写真。ハンズオン参加者はもっと多かった。指の形はElmのEらしい)
やったこと
- Elmな人の発表を聞いた
- チャットアプリを実装する課題をやった
自分のツイート
Elm入門者向けハンズオン に参加を申し込みました! https://t.co/sFJwasXaLn #elm_tokyo
— gaaamii (@gaaamii) 2017年12月27日
#elm_tokyo 遅刻します!13時くらいになります!すみません
— gaaamii (@gaaamii) 2018年1月27日
https://t.co/lw0UMN7EIJ すごい #elm_tokyo
— gaaamii (@gaaamii) 2018年1月27日
#elm_tokyo elm-repl で型シグネチャって書けないんでしたっけ?
— gaaamii (@gaaamii) 2018年1月27日
#elm_tokyo 関数ちょろちょろ書いてreplで試すのは普通に ./Hoge.elm に module Hoge exposing (..) みたいなのを書いておいてrepl内で import Hoge すればいいのか
— gaaamii (@gaaamii) 2018年1月27日
#elm_tokyo インデントに厳しい
— gaaamii (@gaaamii) 2018年1月27日
パイプ理解 #elm_tokyo
— gaaamii (@gaaamii) 2018年1月27日
https://t.co/ncTpNOGYUL #elm_tokyo
— gaaamii (@gaaamii) 2018年1月27日
感想
- Fringe81さんのUniposというプロダクトでElmが採用されているという話に衝撃を受けました
- Elm今後も書いていこうという気持ちになりました
- ABABさんにめっちゃ丁寧に教えてもらって感謝してます
- いきなり実装は難しいという意見はあったけど、座学の時間を長くしたとしても難しいことに変わりはないと思うので、今回のように最低限の説明→実装という形は良かったと個人的には思います(メンター陣がいなかったとしたら厳しそうだけど)。
Elm Tutorial 読んでる
これ読んでる。
なんか趣味でReduxやろうと思ったけど、せっかく趣味なんだから何か変わったものを触りたいということでElm触ろうとしている。シンプルっぽくてよさそうだけど諸々理解が追いついてない。自分用Markdownエディタ作るところまではなんとか頑張りたい。
