読者です 読者をやめる 読者になる 読者になる

レガシーコード生産ガイド

私に教えられることなら

Elmの練習に簡単なインスペクタ付きSECDマシンLisp処理系を作った

(追記) 触れるようにしました

github.com

タイトル長くなりそうなので簡単に書きましたが、ツリー状に値を見れる簡易インスペクタを持ったLisp処理系を書くことで

  1. 静的型付き純粋関数型言語である程度複雑なデータ構造を扱うロジックを書く練習
  2. Elmアーキテクチャである程度複雑なインターフェイスを構築する練習
  3. 言語処理系のデバッガ・インスペクタのインターフェイスを作る練習

をしようと目論みました。


LispInElm

こんなかんじに動作します。

ドット対・可変長引数・マクロなどはありませんが、mapなどを定義できる程度には動きます。

客観的な技術の話は特に無く、あとはプログラミング時の主観的な心境の話です。

感想

FizzBuzzやTODOリスト程度じゃ何の練習にもならないんだけど、題材まで新しい事に挑戦するとどこに問題があるのかわかりづらくなる、ということで手に馴染んだSECDマシンLispを書きました。最初はパーサをLL(1)で直に書き始めましたが、PEGパーサコンビネータのできそこないみたいになったので、思い切ってPEGパーサコンビネータそのものを書きました。その方が楽でした。

1. ロジックについて

型検査・直和型・カリー化があると本当に楽でした。忙しいので毎晩30分ずつほど作業を行い、時には半分寝ながらコードを書いていましたが、型に導かれてコードを書くことができました。

またリファクタリングがとても楽なので、ある程度汚くなることを気にせずに一気に書けました。汚いコードを許容し続けるのは問題ですが、同時に気にしすぎて手が動かなくなることもあるので、後からリファクタリングできるという安心感はやはり重要です。テストをしっかり書けば良いという話ではありますが、自分が適当に書いたテストより型検査器の方が信頼できます……。

2. Elmアーキテクチャについて

関数合成でスッキリと簡単に書けるところが殆どでした。しかし一方で、何らかの状態を(概念的に)持つコンポーネントの組み合わせ次第では、極端に難しい・作業量が多くなる部分がやはりありました。

単純なツリーの上下関係なら、updateをMsg -> Model -> ( Model, Cmd Msg, Out )として、Outを持たせることで解決します。(参考:Elm設計メモ)

しかし、例えば「UIのテーマカラー」みたいに、上から下まで全てのコンポーネントに作用する状態(の概念)を楽に表現する方法がわかりませんでした。

考えられる解決策として、まず単純に上から下まで全てのView生成関数にグローバルなView用Modelを引き渡すやり方があります。view : Model -> Html Msgみたいなものを全てview : Config -> Model -> Html Msgのように変えます。

これが最も正しいやり方ではないのか、とも思えます。上から下までグローバルな状態を引き回す必要があるなら、そう書くのが正しい構造では、と考えられるからです。Lisp処理系ロジックの方でもですが、変に小賢しいやり方で楽に書こうとするより、きちんと木構造を考えてモジュールを分けたほうが結果的に楽になる場合ばかりでした。

しかしそうは言っても、やっぱりもっと楽に書きたいです。例えばインターフェイスのごく一部にちょっとした外部ライブラリを使うために、アプリケーションのView全体に変更を加えるのは、かなり腰が重くなります。

他のやり方として、例えばelm-mdlが採用している、コンポーネント別の状態を辞書で管理するやり方があります。それぞれのコンポーネントにIDを与えて、どのコンポーネントをupdateするのかという情報のみ上に伝えていきます。

メリットは、コンポーネントを追加してもView用のModelを変更する必要が無いところです。特にボタンや入力欄などの、数が増えやすいコンポーネントそれぞれに対していちいち状態保存用のModelを書かなくていいのは楽です。

デメリットは、被らないように気をつけながらIDを手で管理しなければならないことです。純粋関数型で、全体を変更せずにViewのローカルな状態を管理するためには、このIDの管理が最終的にネックになる気がします。特に、汚いコードを許容しない、クリーンなアーキテクチャで達成するのは難しそうです。

(なお、テーマカラーに限れば、styleタグを生成すればいいだけです。コードに不安定さを持ち込んでよければ、scriptタグ生成とportでどうにかする手もあります。ローカルな状態の、全体に変更が波及しない管理ができないわけではないです。)

今後

  • ElmでCharを使うのはあまりパフォーマンス的によろしくなく、Regexを使ったほうが良いとid:jinjorさんに教えてもらったので、パーサコンビネータを書き換えて速度差を見てみたい
  • ML→SECDマシンコードのコンパイラを書きたい
  • SECDマシンのレジスタもImmutableデータ構造なので、Time Travelデバッガが楽に実装できそう。作ってみたい

などなどありますが、一番関心があるのが「Elmのコードは、型やフォーマッタのおかげで時間が経っても読みやすいのではないか」という推測なので、今回書いたコードを完全に忘れるまで放置してみたいと思います。

2016年の大晦日から書き始めて、毎日30分程度を目安にコツコツと続けて、約1ヶ月かかりました。(3/4ぐらいはインターフェイス実装にかかりました……)

今まで、サイドプロジェクトは2週間ぐらい毎日3,4時間夜更しして書くスタイルでしたが、終わった後非常に疲れてその後何も書かない期間が生まれてしまっていました。その期間が嫌だったので今回は少しずつ書いてみましたが、うまいこと気力が残ったまま終わることができました。このまま次のプロジェクトに移ります。がんばるぞ〜〜

広告を非表示にする