Hash λ Bye

Haskell, Clojure, MLなどの話

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

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はよっぽど親を殺されたりしない限りずっと好きだと思う)

ClojureのS式になんでも書けるという話

S式という記法(?)はほんとになんでも書けると知った。

翌日追記: S式というかReaderがすごいっぽい。

例えば

(def japanese
  '(式のなかに
    日本語を書くと
    そのままシンボルになる))

とか

(defn 二倍
  [x]
  (* x 2))

とか書ける。

それにmutil line stringも

(def multiline
  "複数行の
文字列も
そのまま
改行を含んだ
Stringに構築される")

こんなふうに書ける(Pythonぽい)。

何が嬉しいって、 日本語で書いた文章がS式として構造化できるところ。 すごくない?

昨日はそれの事実を確認してとても興奮しました。

Haskellでもquasi-quote使えばできるのだろうけれど、 ClojureのASTと比べると複雑に見える。 GHCのバージョンアップでASTの仕様が変わる可能性よりも、 ClojureのASTの仕様が変わる可能性はずっと低そうだし。

Haskellerの好きなところ

僕はHaskellが好き。

 

なんだけど、同じくらいHaskellもくもく会の常連の人達が好き。

 

ちょっとした型についての質問から、いつの間にかホワイトボード上で証明とか書き出して「あー、ほんとだー、そういうことかー」って言ってる感じが好き。

 

解らないことがあったら自分で検証するっていう、問題に対する誠実で真っ直ぐな態度を持っている人達が好き。

 

Haskellではそういう解らないところを形式的に検証する方法や知識がよく整備されていると思う。(とても難しいものもあるけど)

 

きっと他のコミュニティにもそういう人はたくさんいる。でもHaskellもくもく会にはそういう人達が間違いなくいるのを僕は知っている。

なのであのコミュニティが好きだし、そこにくる人達を尊敬している。

 

僕も真摯に問題と向き合って、reasonableなHaskellの良さを活かしてもの作りができるようになりたい。