Hash λ Bye

Haskell, Clojureなどの話

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の良さを活かしてもの作りができるようになりたい。

Haskellの代数データ型をJava的なインタフェースと捉える

Haskellの代数データ型は僕にとってJavaのインタフェースに近い。

データ型がインタフェースでそのデータを受け取る関数がインタフェースのメソッドに相当する。 データをパターンマッチで分解して値コンストラクタ別の関数定義をするのは、 インタフェースに対する実装を与えているものだと考えている。

data Maybe a

mapMaybe :: (a -> b) -> Maybe a -> Maybe b

Maybe aというデータ型があるとする。これがインターフェース。

mapMaybeという関数があるとする。これがメソッド。

インタフェースに実装を与えていく。

まずはデータ構造としてのインタフェース実装を与える。

data Maybe a = Just a | Nothing

mapMaybe :: (a -> b) -> Maybe a -> Maybe b

ここでは Just aNothing という2種類の実装が Maybe a というインタフェースに対して与えられた。

次に関数としてのインタフェース実装を与える。

data Maybe a = Just a | Nothing

mapMaybe :: (a -> b) -> Maybe a -> Maybe b
mapMaybe f (Just x) = Just (f x)
mapMaybe f Nothing = Nothing

mapMaybe の具体的な定義が各インタフェース実装ごとに記述できた。

mapMaybe のユーザから見れば、 Maybe a の実態が Just a なのか Nothing なのかは意識する必要がない。 これが抽象化されているということ。

オブジェクト指向言語ではvirtualmethodなどを定義して、実行時に実装へと ディスパッチ する。 それと同じように上記の例ではパターンマッチを使って値コンストラクタを判定し、 ディスパッチ している。

この考え方で割りと上手くいく。ライブラリでも作らない限りはこの考え方で多くのことが抽象化できる。 しかしユーザが拡張可能なインタフェースを定義しようと思うと、問題もあったりする。

Expression Problem

Maybe a の値コンストラクタを増やした、つまり Maybe a インタフェースに新しい実装を与えたときに、 Maybe a にパターンマッチをかけている関数も修正しなければならくなるし、再コンパイルが必要になってしまう、という問題。

これを解くために型クラスを使ったインタフェースの定義をしていくことになる。

で、tagless finalってなんだろうというところが今。

追記

先日「型クラスはインタフェースなのか否か」みたいな議論があった。 型クラスを正しく捉えようという目線からは、「インタフェースじゃない」という意見があった。 型クラスをファーストステップとしてアナロジーで理解しようという目線からは、「インタフェースみたいなもの」という意見があった。

視点が違えば違う言い方になるよな、というだけなのでどちらでもいい。

僕は fumieval さんの記事にあるように 「性質の表現」くらいにしか考えていないので、あんまりアナロジーとして見ていない。

といいながらこの記事ではADTとインタフェースのアナロジーを語っているところに何か破綻があるような気もするけれど。

Clojureでの副作用の表現について

自分が悩んでることを拙い語彙でぽつぽつ綴るので、誰にも伝わらないと思う。

DBアクセスする機能を実装したいとする。

java.jdbc を使ってDBアクセスレイヤを実装することになるのが普通かなと思う。 Clojure関数型プログラミング言語なので、組み立てに使う部品(関数)はできるだけ純粋な方がいい。 DBからのデータ取得やデータ更新を行う手続きもできるだけ副作用のない関数で組み立てたい。

DBアクセスする手続きなのにそんなことできるのか? と思う。

Haskellのように独自ASTとinterpreterのパターンならそれができる。 Clojureでも同じような構成になるのだろうか。

いくつかアプローチがありそうだ。

  1. 副作用を起こす関数は高階関数で受け取る
  2. 副作用を起こすコマンドを生成する純粋関数を合成し、interpreterで副作用を発行する

基本的には高階関数で解けるのが一番低コストでよいと思う。 高階関数を使ったDIだと捉えるとわかりやすい。

ただ適用する関数が色々な種類の副作用関数を要求するようになると突然複雑になる。DIで言えば注入する依存が肥大化している状態。 気軽に依存が満たせなくなるので、とても腰の重いインタフェースができる。

それを補うために関数の辞書を渡すことになってしまう。しかしインタフェースが抱える複雑さは変わらないので、実はあまり解決になってない。

2案

あるパラメータを渡すと「印字せよ」とか「INSERT文を発行せよ」という命令を返す関数を中心に組み合わせる。 命令は実行されるまで何の副作用も生まない。なので、その関数を呼び出しても、何か副作用が起きるわけではない。 実行すれば副作用が起こる命令が手元にあるだけだ。

これはDIとは完全に切れ離された、より純粋な設計になる。 しかし一方で、命令を実行する別の機能が必要になるため複雑さはそちらになお宿っている。

が、ビジネスドメインの記述に徹することができるのは保守上のメリットがある。

対して、命令と実行を受け持つ関数はなかなか軽くもシンプルにもならないだろうと思う。

それでもドメインを純粋に保てるのなら2案になるのかな。

設計プランは後ほど追記 - 4/23 まだできてない Clojureで遊んでいる場合ではない感じに。。。