Hash λ Bye

Haskell, Clojure, MLなどの話

好きなプログラミング言語の好きなところについて思った

改めて最近実感すること。

Haskell, Elm, Clojureほんと好き。

Scala勉強しなきゃなーと思いながらClojureを触ってしまうことが多かったのだけれど、 その理由が少しずつわかってきた。

いい言語たち

いままで少しだけ触れてきたJava, Python, Scala, Goはいずれもとても大きなユーザを抱えている。 どの言語もたくさんのユーザを得るために現場で使えるようなエコシステムをどんどん投下してあっという間に大きなユーザベースを獲得した。

プログラミングのしやすさを大事にして、誰でもすんなり入門できるように設計されている。 僕が入門できるくらいだから本当に敷居が低くて、けれどどんなやりたいことも実現させてくれるいい言語ばかりだ。 押し付けがましい思想も少ない。

たのしい言語たち

一方でHaskell, Elm, Clojureはちょっと独特だ。

HaskellではMonadicなプログラミングやSTM, デフォルト遅延評価など今まで触れたこともないようなパラダイムで溢れていた。

ElmではElm Architectureという一見してみると強い制約のもとでWeb UIを構築しなければいけなかった。

ClojureではREPL駆動開発やtransducer, 状態のオペレーション(STM, Atom, Agentなど)という独特なキーワードが骨子になっている。

(あ、全部関数型プログラミングだ。。。)

いずれも今までの自分には聞いたこともないような概念ばかりで、きっと解らなくてすぐに挫折してしまうだろうと思った。 だけど全然そんなことはなかった。むしろ楽しかった。

やりたいことが手元にあって、それを実現するためにどうしようかと考える時、Haskell, Elm, Clojureではまず素直に落とし込めない。 どんなやり方があるのか調べて、どれがその言語らしい XXX way なのかを知っていかなければいけない。 本当に手間がかかることが多い。

なのになんでこんな楽しいのだろう。進まない。進まないのに。

簡単≠たのしい

その制約のせいか、「できた!」と思った時の瞬間が本当に嬉しい。やっていることは全然大したことない。 SpringBootとかDjangoとか使えば瞬殺なのだろう。

でも、なんというか、その言語の思想に沿うように実装できた時の嬉しさは要求を鮮やかに実現できた時の歓喜とは別の何かだ。 3Dプリンターで模型を切り出すのではなくて、彫刻刀で大仏を彫り出すような変なドグマの混じったカタルシスを感じる。

何ができるかではなくて、どうやるかが楽しい。 ほんと、仕事としてプログラマやっている人間としては全然駄目だと思う。 実際問題、顧客の要求を低コストで十分に実装する能力や判断力の方が絶対にお金は入るはずなんだ。 成果物や収益から逸脱して、作法や流儀や過程に楽しみを見出してコストを支払うなんて多分駄目なはずなんだ。

でも何故だろうか、遠回りなのに美しい道が用意されているように見えるプログラミング言語ばかり好きになってしまう。 マーケットとか人材の需要とかじゃなくて、思想や信念や流儀に見え隠れする怪しさ(妖しさ?)に惹かれてしまう。

本当はScalaPythonやGoに触れて経験を積んだ方がきっとすぐに応用に入れる場面も多いはずなのに。 どうもそういう打算が働かず、ただただ楽しいと思うプログラミング言語に傾倒していくのを自ら止められずうろうろしている。

幸い、Haskell, Elm, Clojureはプロダクションでの採用事例も増えてきている。いずれ自分が書いたHaskell, Elm, Clojureのコードがプロダクション環境で動く日が来たらいいなあ、と思う。

Erlangとかもなんだか光背が見える言語だ。。。気になる。

ネットワーク機器の制御とかでHaskell, Elm, Clojureあたり使っている会社の仕事とかあったらいいなー。 ネットワーク(特にバックボーン)業界はだいたいPythonJava, Goなのでもう少し僕が好きなプログラミング言語の実装増えてこないかなあ。

(自分で書いていけってことか。。。)

技術書を読む時の問題意識について(答えはまだない)

何か学習したい、と思う動機があるとする。

現在の僕の場合は「仕事の関係でLinuxカーネルについておさえておきたい」とか。

学習したいので何らかの書籍を参照することになる。

問題

読んでいる内容が頭に定着していない感覚

購入した詳解Linuxカーネルでは「メモリアドレッシング」の章で、 ハードウェアの仕様も含めて、章タイトルについての詳細な説明がなされている。

どうもこれを読んで頭に入っていると感じない。

そもそもセグメンテーションて何だ、というのがわからない状態で読み出す。

何故か

そもそもメモリアドレッシングについての問題意識がない。 気にしたことがないし、それについて知りたいと思うような動機づけの体験がない。

なので読んだところで「読まされている」状態から抜け出せない。

逆説的に、メモリアドレッシングにまつわる問題に過去出会っていて、 どうしても詳細まで掘り下げたかった人はよく頭に入ることだろう。

書籍を読んでいて「なるほど」と思うには、 まずそれを解きたいと思うなりに本人の中に謎みたいなものが定着している必要がある。 これまでの体験により伏線だけが溜まっている状態だ。

書籍を読むことで体験した事実に対する説明がなされて、 伏線回収がおこなわれる。 この時に初めて「なるほど」と納得する。

僕が頭に定着する、と言っているのはこの変化のことを指す。

つまり、僕は伏線を持たずに伏線回収の説明をされている状態なわけだ。

どうしたらいいのか

2つくらい思いついた。

1. 愚直にわからないところを調べる

読み進めるにあたって解らないところを新しく問題意識の対象と捉えて解きにかかる、という考え方。

Intelアーキテクチャまで知らないとだめなのでは。 いったいどこまで深く潜ったらいいのだろう。

DMACも調べないといけない。

足元に広がるハードウェアの世界は広すぎて探索の意欲が減っていきそうだ。

2. 解ったことを整理して解らなかったところを記録する

たぶん現実的な解法。

「あとでがんばる」という判断で不明なところを棚上げにして進む。

詳解Linuxカーネルは頭から読んでいくような章立てに必ずしもなっていないし、 これからも細部でわからないところが想像以上に出てくるだろう。

これにいちいち付き合って深堀していると前進感も得られないし、 きっとどこかで挫折するだろう。

シナリオを進めるためにはいったん「逃げる」コマンドを使うことも必要だということかな。

大事なのはあとで「戻ってくる」ことだけれど、うーん、戻ってくる気が起きるかどうか。。。。

まとめ

書籍を読んで納得するには、知りたい問題が頭の中に既に用意されている方が良い。

といっても書籍に書いてあること全てに対して問題意識を持っているということはまず無い。

なので納得に至らないなら、一度進んでまた戻ってくる方が精神衛生に良さそう。

結城浩さんの数学ガールは問題意識の発露からストーリーに乗せて語るので、読者が露頭に迷うことが少ないのかな、と脱線しながら思った。

小さく作ることとモチベーション

Minimum viable product - Wikipedia

小さく作りながら進めるのがうまい人は作業ステップの分割ではなくてゴールの分割に優れているなあ、と思う。 最低限のユーザが試すことができる機能のみを搭載したプロダクトをMVP(Minimum Viable Product)というらしい。

ユーザストーマッピングの本にも書いてあった。

僕が実例で見て痛感したのはRui Ueyamaさんの動画。

www.youtube.com

小さいけれど動くものを作る。 できたものをテストして、また機能をリッチにしていく。

Rebuild.fm #153にUeyamaさんが出ていた回では「インクリメンタル」と表現していたアプローチ。

なんでこれが良いのか

僕自身のモチベーションが維持できる。 小さくてもフィードバックが得られるから。

小さくても動くという達成感がいかに大きいか、最近Haskellでコード書いていてつくづく思う。 少し書いたら小さなテストを作って確認していく。

「ここまでは動きそうだな」というのが安心感として得られる。

安心!

安心が大事だ。モチベーションは精神性に根ざすので、安心という心の状態がモチベーションに寄与するのは、なんとなくreasonableな気がする。

安心して進めたい。うんうんと頷きながら進めたい。わけがわからなくなって疲れて飽きてやめたくない。

そのためにゴールを小さく分割して作っていこうよ、という活動が僕なりのMVPの効用だ。

はまりがちなこと

頭にあるものを一気に吐き出そうとすると、前述の進め方を忘れてしまうことがある。

とにかく全部一度作りきろうとするのだ。

すると一気に作ったあと一気に動作を検証していくことになる。 一気に作ったものがきれいに動作することは大抵なくて、長いデバッグが始まることを意味する。

フィードバックを得る機会は先延ばしになり、モチベーションは消費されるだけになってくる。

今度はだんだんデバッグが辛くなってきて腰が重くなってくる。 こうなるともう赤信号だ。

今までこういう隘路にはまって続かないことが多かった。

小さくゴールを分割するということ

これは難しい課題だ。

問題領域がよく解っているならゴールの分割はわけないだろう。 でもインクリメンタルにモノを作っていく時、大概の場合は作ろうとしているモノの全体像はわからない。 自然と探索的にゴールを切り取っていくことになる。

これに定石があるようには思えない。なので都度頭を悩ませながらゴールを切り取っていくのだろう。

けれど、間違った判断をしていることに気づけるかもしれない。 例えば、ゴールを分割するのではなく、作業工程を分割するような間違いだ。 ユーザーストーリーマッピングの本では、とても大きなケーキを作る作業を例えにだしていた。

ゴールを小さく区切るMVPの考え方に従うと、小さなゴールとは例えば1/16くらいのサイズのカップケーキのようなものだという。

誤った分割をしている場合は、大きなスポンジを作ることだという。

大きなケーキを工程で分割してしまうと後者の過ちを踏むことになる。 対照的にMVPに従ってゴールを分割すると、前者のように小さいけれどユーザにデリバリできる単位が成果になる。

フィードバックを得やすいのは明らかに前者、ということだ。大きなスポンジだけ渡されても、それはケーキではないから誰も評価できない。 一方小さなケーキは小さくてもケーキだ。食べてケーキとしての出来栄えの感想を述べることができる。

幸いにしてソフトウェアは小さな部品を合成して大きな部品を構築できる性質を備えている。 小さなケーキを合成すると大きなケーキが作れる。

これを利用しない手はない、ということらしい。

まとめ

小さいけれど充分に動作する機能を反復的に作っていく。 これはモチベーションを維持していくことにとても効果がありそうだと思った。

もちろん難しいのはゴールの分割する思考法だ。

それでも三日坊主になりがちな僕でもモノを作り続けられるかもしれない唯一のアプローチではないかと思っている。

仕事で使った言語

僕もまとめておこう。

Java

書いたコードの量はダントツに多い。

リフレクションお化けにハマったり、JavaEEにハマったりした。

いまはJavaに興味なくてJVMに興味が移ってしまった。

JavaScript

jQueryが流行ってた頃に一度検索バーの補完用モジュール書いたりしてた。

その後、一度別のツールでReact使ったりした。

Python

Webアプリを書くときに使った。 良い言語だと思う。関数型プログラミングを少しかじれるのは魅力なんだけど、イミュータブルデータ用のAPIが少なくて結局関数型プログラミングを活かしきれないと結論づけている。

Go

簡単なログ収集ツールとかテキストデータの解析で使った。 プログラミングスタイルに慣れていなかったので、意図を関数と構造体で表現するのにとても苦労した。 もう少しCとか書いてから再入門したらきっとmind blowingな発見があるのだろう。

Haskell

ちょっとしたデータ解析のコマンドラインツールで使った。作ったバイナリをポータブルにできなかったので、作るだけ作って配布は断念した悲しい経験。

Clojure

テストスクリプトのスケルトンを出力するツールを作るのに利用した。

安易にNPEを引いてデバッグに苦労した記憶。すごく好きな言語だけど、もう少しREPLを使った開発に慣れないと安定して動くプログラムを組むのに時間がかかるなという印象。

まとめ

あたりを満たせてる言語だと僕の場合はすんなりプログラムが組めるみたい。

実行ファイルの配布やメンテナンスを考えると実はScalaあたりが自分にフィットするのかな、とは思う。

Haskellが好きなのは変わらないのだけれど、仕事で使うにはまだ僕自身の課題が多そう。

なのでやっぱりScalaなのかなー。

ちなみにプライベートではHaskellのalexとhappyを使ってSMLの構文解析器書いてる。コンパイラ技術はとても楽しい。

左再帰を含む構文解析むずい

やろうとしていること

Haskellのparsecを使ってSMLの構文を解析し構文木を生成する。

やっていること

SMLの構文解析はいろいろステップがある。

  1. リテラル (special constants)
  2. 識別子 (identifier)
  3. 型注釈 !!イマココ!!
  4. パターンマッチ
  5. 宣言
  6. モジュール構文

リテラルや識別子はなんとか倒して、いま型注釈の解析に取り組んでいるところ。

苦戦しているところ

この型注釈の構文解析で例の問題に突き当たった。

再帰問題だ。

SMLの型注釈の構文はこんな感じ。

ty ::= tyvar
       { <tyrow> }
       tyseq longtycon
       ty -> ty'
       ( ty )

tyrow ::= lab : ty <, tyrow>

上記の中でもいま苦戦しているのが、

tyseq longtycon

というところ。

tyseq

とは0回以上tyにマッチするtype constructorへの引数を表す。最後にtype constructorにマッチさせる。

0回以上tyにマッチするかどうか検査するためには、再帰的にtyを呼び出すが、これが無限再帰を起こす。 つまり構文木の左側をぐんぐん掘り進めていってしまう。

これは素朴な数値演算式でも起きうる問題だ。

expr ::= expr + expr

などでも再帰的に解析が走るためparserが停止しないというよく知られた問題。

今回のケースではlongtyconがoperatorとなりtyseqがoperandとなるためポーランド記法のようなものと捉えることができる。

幸いにしてText.Parsec.Exprにはこのようにoperatorがpostfixとして出現するケースを式として解析する技がある。 しかしこのlongtyconは解析の過程で動的に発見される。果たして素直に使うことができるのだろうか。。。

市井の例を見るとpredefinedな算術演算子のみを扱っているケースが多くてあまり参考にならない。

この先

parsecでこのままくのか。

それとも、Alex + Happyでparser generateするのか。

このあたりがわからなくなってきている。

IdrisやElmはparser combinatorをある程度自前で実装して構文解析しているのを見ると、やはりparsecでも左再帰を解決しつつ解析できそうな気がするのだけれど。

追記

Adam Wespiser Parsing

If our language required a lot of left-recursive parsing, Alex & Happy would probably be a better choice.

こんな記述があった。 この記事ではschemeは比較的構文がシンプルなのでparsecでいくよ、と言っていた。

はて、僕が解析しようとしているSMLはどうだろう。とても左再帰が多い構文だ。

diehlさんも最初にparsec使っておいて結局Alex + Happy使っていたな。

Write You a Haskell ( Stephen Diehl )

いまはparsingの技術を掘り下げるよりも前に進むことを考えよう!

ということで、再びAlexでの字句解析をインクリメンタルに進めよう。

Standard MLの定義を読み始める

自分の欲しいDSLを作るにあたって、構文として参考になりそうなものを探した結果MLが良いのではとなった。 特にStandard MLが良さそうだ。

OCamlより個人的には素直な文法に感じた。Schemeと違ってTypingもいずれできそうな構文だった。Haskellのようなオフサイドルールもなくて、parsingが楽かもしれないと思った。

なにより定義という一次ソースがあるので、とりあえずそれを読むことから取り掛かれる。

ということでまずはなんか一個くらい構文要素解析するとこまでいきたい。

参考にしてるもの

Standard MLの定義

http://sml-family.org/sml97-defn.pdf

Haskellコンパイラ

http://www.stephendiehl.com/llvm/

Haskellで書かれた実用コンパイラ

https://github.com/elm-lang/elm-compiler

Standard MLの使い方についての参考

http://walk.wgag.net/sml/index.html

型システム入門

https://www.amazon.co.jp/dp/4274069117

SML#の解説(日本語でSMLの構文定義が読めるという意味で有用)

http://www.pllab.riec.tohoku.ac.jp/smlsharp/docs/3.0/ja/manual.xhtml

Strongly static types, not for every task なのは何故なんだ

Clojure Rationale

このページにこんな文があった。

Pure functional languages tend to strongly static types * Not for everyone, or every task

「静的型付けは全てのタスクに適しているわけではない。」

そのタスクとは何を想定しているのか、ここでは語られていない。

僕はHaskellが好きだし、GHCの型システムがとても好きだ。 コンパイラに怒られながら、コンパイラと協力してプログラムを組み上げていくのはとても楽しい。 データの構造や関数の要求する仮定・制約を忘れっぽい僕にはとてもいいシステムだ。 だから静的型付けは好きだ。

だからこそ、静的型付けがふさわしくないケースを知りたい。

Richがここで言っているように、静的型付けではふさわしくない領域があるはずなのだろう。 それが何なのか知りたい。

関数の再利用性のために

この辺 が背景なのかな。

Rich的には関数の再利用性を促進させるために、具象型に関数を縛り付けないということなのだろうか。

Clojureではユーザ定義型を定義するよりも、リストやマップを頻用して、 関数の再利用性を上げることができるから型が不要ということ?

それでもやっぱり静的型付けではふさわしくない問題が何なのかはまだわからない。

Python書いている時

Pythonも静的型検査がない。 比較的Pythonスクリプトを書く機会があるので自分の感覚を思い出してみる。

「これはHaskellだと無理だなー」って思うようなことがあっただろうか。

小さなスクリプトを書く

bashの延長だと思って書捨てる時に静的型検査は要らないと思うことはある。 そういう時ってあまり問題の構造とか考えなくって、 頭にある「手続き」をPythonのコードに書き下しているだけ。

そんなふうに、問題の分析を飛ばして動くものを書いている時、確かに静的型付けが必要だとは思わない。

中規模のWebアプリを書く

データモデルを考えて、適切なクラスを定義、ってなる。 もう問題の構造を捉えようとしている。

この時初めて型による名前付けが欲しくなる。

付けた名前はプログラマ間で共有することで、意図や意味を伝えていく必要がある。

そのために型注釈が必要になってくるし、それらの意図や意味に沿わない操作を禁止するために静的型検査が必要になってくる。

やっぱり規模が増せば自然と静的型検査を要請してしまう。

PythonでWebアプリを書いていた時、やっぱりそう感じたのを思い出した。

Clojureでも同じように感じるか?

感じると思う。

少なくとも僕はデータの構造をすぐ忘れてしまう。

だから長らく自分がメンテしていないコードを見たらきっとどんなデータを扱っているのか解らず苦しむだろう。

それでもClojureが好きなのか?

なんか好き。なんだろ。 大きいもの書き始めたら嫌になったりするのだろうか。

ふとClojure書きたくなる謎の魅力がある。不思議な言語だ。 これが中毒性か。

(Haskellはよっぽど親を殺されたりしない限りずっと好きだと思う)