2012年10月27日土曜日

Clojureのエラー処理

Clojureでエラー値を返すかもしれない関数ってどう書くのだろうとちょっと考えてみる。

Scalaだと例外はあまり使わず、EitherやValidationを使う派。
やはり、コンパイラの恩恵を受けることが出来る書き方が良い。

しかし、Clojureは動的型付けの言語。
コンパイラの恩恵は特にない。

なので、 どのような値が返ると扱いやすいかを考える。

Clojureは基本的に
  • nil返す
  • 例外投げる
の2通りだと思う。

Clojure標準のライブラリはnilを渡してもそれなりに動作するし、if-let,when-let,fnilなどの関数も用意されている。
しかし、nilだとどのようなエラーだったのか判別がつかない。
このような時に例外を使って、エラーの種類によって投げる例外を変えれば良いのだと思う。

Clojureの例外を投げるような関数は以下のように書ける。

この場合は、Java APIのIllegalArgumentExceptionとIndexOutOfBoundsExceptionを使った。
しかし、何時でもJava APIに自分が欲しいExceptionが定義されているわけではない。
このような時は、独自の例外を作るだろう。

駄菓子菓子!

Clojureでclassを定義するのはとても面倒。
nsやgen-classを見てもらえればわかると思う。
Throwableがinterfaceならば、deftypeやdefrecordが使えたのだけれど・・・

例外を定義する部分だけJavaで書いたほうが楽かも。

leiningenはJavaもサポートしているので、共存させるのは苦ではない。

結論


基本的にnilを返す。
独自の例外を定義したかったらJavaを使うと楽

2012年10月13日土曜日

Clojureでモナド内包表記とStateモナド

cljsbuildのcrossoversという機能を知らない所為で今まで無駄に苦労してきましたが、これでマクロが使えないという制約から解き放たれました。
万歳!cljsbuild!

マクロが使えるようになってまず一番最初にやることは何か?
もちろん、モナド内包表記の実現ですね!

HaskellのdoよりはScalaのforの方が慣れているのでこちらを採用しました。

動的型付けの言語でのモナドの実現はいくつか見てきましたが、明示的にコンテキストを指定するタイプはあまり好かないので、ClojureらしくProtocolで表現することにしました。

Monadic Protocol


通常モナドはunitとbindが実装され、モナド則
  • bind f (unit x) == f x
  • bind unit m == m
  • あともう一個なんかあった気がする
を満たしますが、動的型付けの言語でunitを実現するのは面倒なので、モナドっぽいインターフェースを定義します。

このインターフェースが実装されていれば、モナド内包表記が使えるようになります。
とてもScala的。

モナド内包表記


Lispは神の言語と皆ネタにしていますが、Lispのマクロは本当に強力です。
既存の言語にある構文の全てはLispのマクロで表現できるのではと思えるくらいです。

Clojureの標準ライブラリではパターンマッチが出来ないので、準標準ライブラリであるcontribのcore.matchを使います。

しんぷるいずべすと、MonadPlusなんて知らないのでガードは実装しません。めんどい。

このdo-mマクロがどのように展開してくれるかというと

が

こんな感じになります。

実際に動かしてみます。

動いたー!
素敵です。

Stateモナド


私が現在製作中のアプリケーションでは、レコードを更新する関数が多数定義されています。
これは、状態をとって、新しい状態を返す関数ですが、少し問題があります。

一つ例を示します。

このような関数が定義されていた時、右下へ移動する関数を定義するのに->>マクロを使い、手続き的に書くことができます。

しかし、このrightでの計算結果、つまりxを使う関数を定義する時、コードはこのようになります。

このパターンが関数内で頻出する場合、一時的なcursorの状態を保存する為の変数が増えてしまいます。
さらに、この場合はxを取得するコストは微々たるものですが、フィールドの取得に複雑なロジックを用いたり、計算値が状態の更新に間接的に使われるだけで、レコードからは取得できない場合があります。

新しい状態だけを返すのではなく、計算値とのペアで返せばこのパターンを解決できます。

しかし、->>マクロのようにネストせずに書けるわけではありません。

そこでStateモナドの出番です!(導入長い!

Stateモナドは状態を取り、新しい状態と計算値のペアで返す関数に対してモナドを定義したものです。

先ほど定義したMonadicをStateに実装してみます。

right, down関数をStateで定義してみます。

この時、square関数はdo-mを使って以下のように定義できます。

ヤッター!

関数の合成が手続き的に書けますね。

まとめ


マクロ強力。Stateモナド美しい。

本当はFreeモナド使おうと思ったけどStateモナドの素晴らしさを伝えたかったので控えました。

これから70個近い関数をStaeモナドに書き換える作業が待ってます。
Freeモナドの資料も作らなきゃですね。
私の代わりに大学のC言語の課題をやってくれるといいと思います。

2012年10月3日水曜日

Clojureへの不満とか

最近はClojureばかり書いているので、良い所ばかりでなく、気になるところも見えてきました。

なにか意見をもらえるかもしれないので書き並べておきます。

セルフホスティングして欲しい


Clojureのソースコードを読むときに、高確率でJavaのコードを読まなければいけないので悲しくなります。
やはり、gen-classとかでは物足りないのかなあ?
それとも、セルフホスティングにはあまりヤル気がないのか・・・・

clojure.langのJavaDoc欲しい


ソースコード追うのが面倒なので。

Protocolをもっと使って欲しい


自分で新しいデータ型を作った時に、既存の関数を使うためにclojure.langの得体の知れないinterfaceを実装するわけですが、Protocolとして存在すればClojureの中で完結するのでうれしいのだけど、これはデータ型がJavaに依存しすぎてて簡単にはいかないんだろうなあと。

関数が返す値は適用する値と同じデータ型で返して欲しい


特にStringとか。
charのシーケンスで返ってくるのが悲しい。
これはなぜだろう、効率のためかな?
どこかで読んだ気がしなくもない。

いっそのことClojure独自に文字列作るというのは・・・・まあ、それはそれで面倒臭そう。

Scalazは独自にCordというデータ型を定義しています。
さすが、尖ってる。

insertしたい


ArrayListやLinkedListを使えばいいのですが、functionalでpurelyなdata structureが欲しいわけです。

Arrowが欲しい


Haskellのfirst,secondに当たるものが欲しい。
分配束縛したくない。
もっとポイントフリーなプログラミングがしたいのです。

しかし、compとpartialとjuxtでやりすぎたコードを書くのには注意です。

if-let, when-letが1つの変数しか束縛できない


なぜ複数束縛できないのか・・・・・

要素を1つだけ持つlistとmapとmapcatまたはforを使って、Maybeモナドっぽいことは出来ます。
あとは(fn [a] (if (nil? a) (list) (list a)))のようなものがあるといいのだけど。

do記法、for内包、コンピュテーション式にあたるものが欲しい


for内包はあることにはあります。
しかし、これはSequableでSequentialなデータ型(JavaのIterableとIteratorのようなもの?)の為のもので、モナドの為のマクロではないです。

Maybeモナドやdo記法はalgo.monadsで提供されていますが、あまり設計に納得がいかない。
私はあるデータ型に対してモナドを定義したいので、Protocolをベースに設計したものが欲しかった。

限定継続が欲しい


do記法がないなら限定継続を使えばいいじゃないと昔の貴族は言ったものです。
限定継続があれば括弧を減らせるような構文がいろいろと簡単に定義できそうだなあと思い挙げてみた。 

delimicといものがあるらしい。

まとめ


私はいままではScalaばかり書いていましたが、いまはClojureばかり書いているのでこのような記事を書いています。
実際には2年前くらいから少しずつ書いていましたが、これほどClojureを書いてる期間はありませんでした。

Scalaに触れ、さらにScalazに触れたことで完全に関数型プログラミング信者になってしまいましたが、実際に有用な技法を多く知ることが出来ました。
それをClojureで実現するために、このような贅沢な不満が出てきました。

Clojureはまだまだ若い言語だと思うので、あれもこれもと求め過ぎていると思います。
なので、今後私が期待しているものに発展してくれるようClojureに貢献していきたいです。

2012年9月13日木曜日

ClojureScriptのテスト

皆さんテスト書いてますか?
私はあまり書いてません。
テストは大事です、書きましょう。

私は現在ClojureScriptを使っているのですが、JavaScriptでのテスト方法とかよくわからないし、Clojureでテスト書きたいので、ClojureからClojureScriptを読み込もうという考えに至りました。

が、しかし。
requireなどを使って、ClojureScriptのコードを読み込むことはできませんでした。
なぜなら拡張子がcljsだから。
load-fileなども使ってみましたが、無理でした。
ClojureScriptのコードの中でrequireを使っているからです。

どうにかして拡張子がclj,classのファイル以外を読み込むことはできないのかと調べてみたところ・・・・

https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/RT.java#L403

ベタ書き・・・!
差し替えることなど到底無理っぽい。

ならば作るしかありません、cljファイルを。


copyしてuseしてdeleteする、悲しみあふれるコード。
cleanupのところはtry-finallyした方が良いかも。

clojure.testはfailした時にマクロを利用して述語に渡した値を展開してくれたりでなかなか便利なので割と好きです。
しかし、ここまでして使うのもどうかなと。

素直にJavaScriptのテストフレームワークを使うとか、assertで済ませる(ClojureScript本体のテスト方法)などでいいかもしれません。

2012年9月10日月曜日

Scalaでdo記法

函数プログラミングの集いで限定継続の話が面白かったので書いておきます。

ScalaでもHaskellのdo記法っぽく書ける。

調度良くScalaでHaskellしてる記事があったので、これを参考にさせていただきます。

\めんどい/

2012年9月5日水曜日

Clojureのinsert

Clojureでlistやvectorのn番目に値をinsertしたい時はありませんか?
Clojureには標準でlistやvectorに対するinsertが定義されていません。
でもzipperにはinsertがあるみたい。
そこでvectorとzipper、どちらのinsertが速いか測ってみました。

コード


結果


ClojureScript

Clojure

思っていたよりは差がつかなかった。

vectorやlistはいつもこんな感じでinsertしてるのですが、もっと綺麗で効率のよい方法があったら教えて下さい。

効率のよいinsertが出来るシーケンスを標準で用意してくれてもいいのではと思わなくもないです。

2012年9月1日土曜日

Machines

先日rpscalaに行った時、@xuwei_kさんにscala-machinesというものを教えてもらったので調べてみました。

Example.scalaを参考に、コードを読んでいきます。

少々書き換えました。

ぱっと見ても全くわからないですね。

IO


副作用(アクション)を表します。
IO.applyは値をcall by nameでとります。
unsafePerformIOを呼ぶことで、アクションを実行します。

bufferFile


ファイルをとってBufferedReaderを返す関数です。
IOでラップしています。

rReadLn


BufferedReaderから一行読み取ります。
readLineは文字列かnullを返すのでOption.applyを使用しています。
これもIOでラップしています。

closeReader


Readerを閉じます。
当然副作用なのでIOでラップされます。

Process


Process[I, O]はI => Oをラップしたものと考えていい・・・・はず。
Process[I, O]はMachine[I => Any, O]のシノニムで、Machine[K, O]はPlan[K, O, Nothing]のシノニムなので、実体はPlanなわけですが、まだ私は理解できていません。
IterateeのIteratee,Enumeratee,Enumeratorの内のIterateeにあたるようなものだと思われます。
なのでストリームの要素はこいつを使って処理していきます。

countLn


文字列をとって1を返すProcessです。

getFileLines


型パラメータAとProcess[String, A]とFileをとり、Procedure[IO, A]を返します。
モナド変換子と同じく、Procedureから値Aを取り出す時にIOにラップされるようになります。
ProcedureはEnumeratorのようなもので、ストリームとそれを処理するMachineを持ちます。
ProcedureはMachineの第一型パラメータとMachine、抽象メソッドのwithDriverを定義します。
Process[I, O]は先述の通りMachine[I => Any, O]と定義されているので、抽象タイプメンバにはString => Any、Machineは引数としてとったProcessで定義します。
Driverでは入力を定義するのですが、当然、入力にはリソースが必要になります。
そこで、このwithDriverではリソースの作成、開放、Driver(入力)の作成、Driverの適用を定義します。
リソースの作成はbufferedReaderを使います。
IO#bracket(f)(g)は処理gの後に必ず処理fが処理されます。
事後処理としてcloseReaderを渡し、Driverの作成と適用をします。
DriverにはrReadLnを使い、入力を適用するapplyを定義します。
最後にwithDriverがとる関数kにDriverを適用します。

exec


ファイルを関数に適用し、実行、出力する関数です。
executeはProcedureの結果の型Aがモノイドである必要があります。
ストリームを処理した結果をaccumulatorに加える関数で合成していきます。

runc


main関数で呼ばれる関数で、裏でunsafePerformIOを呼びます。

CSVファイルの読み込みと要素のカウントを書いてみた。

まとめ


IterateeよりはIOに特化していると思われる。
リソースの処理をProcedureでやっているところとか。
ProcessやProcedureを自分で書くのは、IterateeやEnumeratorを自分で書くよりは楽・・・・な気がしなくもない。

今回は本当にExampleをさわっただけで、詳細はわかっていない。
作者のドキュメントに期待です。