アクティブパターンでエラー処理をする
この記事は F# Advent Calendar 2011 の参加記事です。
JavaやC#のような言語でプログラムを書いていると、処理本体は短いのに、なんでこんなに長くなっちゃうんだろう……ということがよくあります。関数の頭でnullチェックをして、Exceptionをキャッチするためにtry...finallyで囲んで、等とやっていると本体がどこにあるのかわからなくなるということは日常茶飯事です。こういったコードはどこが重要な場所なのかがわかりにくく、メンテナンス性が低いものです。
現代的なライブラリや言語ではできるだけ本質的なところだけを書けるようにいろいろな仕組みが導入されています。
今日はそういった仕組みの一つとして F# のアクティブパターンを紹介します。
社内勉強会のプログラミング添削会(?)で書いたじゃんけんプログラムを例にとって説明します。
まずアクティブパターンを使わないで書いた場合のプログラムがこれです。
Rock,Paper,Scissorsのうちどれかを二つ選んで引数として渡すと、左か右のどちらがじゃんけんに勝っているかを判定して表示するものです。
type JankenHand = | Rock | Paper | Scissors type State = | Draw | Right | Left let Janken l r = match l, r with | Rock, Rock -> Draw | Rock, Paper -> Right | Rock, Scissors -> Left | Paper, Rock -> Left | Paper, Paper -> Draw | Paper, Scissors -> Right | Scissors, Rock -> Right | Scissors, Paper -> Left | Scissors, Scissors -> Draw let parse s = match (s:string).ToLower() with | x when x.StartsWith("r") -> Rock | x when x.StartsWith("p") -> Paper | x when x.StartsWith("s") -> Scissors | _ -> failwith "argument is not in (r,p,s)" let main = function | [|_; l; r |] -> match Janken (parse l) (parse r) with | Draw -> "draw" | Left -> "Left wins" | Right -> "Right wins" |> printfn "%s" 0 | _ -> printfn "fsi Janken.fsx {R,P,S} {R,P,S}" 1 main fsi.CommandLineArgs
アクティブパターンを使った版がこれです。
type JankenHand = | Rock | Paper | Scissors type State = | Draw | Right | Left let Janken l r = match l, r with | Rock, Rock -> Draw | Rock, Paper -> Right | Rock, Scissors -> Left | Paper, Rock -> Left | Paper, Paper -> Draw | Paper, Scissors -> Right | Scissors, Rock -> Right | Scissors, Paper -> Left | Scissors, Scissors -> Draw let (|JankenHand|_|) j = match (j:string).ToLower() with | x when x.StartsWith("r") -> Some Rock | x when x.StartsWith("p") -> Some Paper | x when x.StartsWith("s") -> Some Scissors | _ -> None let main = function | [|_; JankenHand l; JankenHand r|] -> match Janken l r with | Draw -> "draw" | Left -> "Left wins" | Right -> "Right wins" |> printfn "%s" 0 | _ -> printfn "fsi Janken.fsx {R,P,S} {R,P,S}" 1 main fsi.CommandLineArgs
元々 parse という関数だったのが (|JankenHand|_|) という関数に変わり、main関数の中のパターンマッチがそれに伴って変化しています。
最初の実装では main 関数の中で引数のパースに失敗したとき、例外が飛んでプログラムがスタックトレースを吐いて終了するようになってしました 。パースに失敗したとき例外が飛ぶようになっていると、スタックトレースを吐いて死なないようにするためには、parse部分を try...catch で囲む必要があります。しかも、引数のパースが失敗したときにプログラムの正しい使い方を表示するのであれば、catch 説の中に使い方の表示をするコードを書かねばならず、すぐ外側に既に存在している使い方表示とコードが重複してしまいます。
アクティブパターンをつかって処理する場合、パースが正常に完了したときにのみパターンにマッチするため、それ以上特別な例外処理を追加することなく、通常の条件分岐(match式、function式)の中で自然に例外的状況を取り扱うことができるようになっています。変更後のコードではおかしな入力があった場合にはスタックトレースを吐いて死ぬのではなく、使い方を表示して終了します。
このようにアクティブパターンを使って例外処理を書くことは、単に見た目に簡潔になるだけでなく、網羅的でもあります。あらゆるパターンを網羅していない場合にはコンパイルエラーになるため、XXの場合のケアを忘れていたためプログラムが落ちた、ということが起こり得ないのです。
パターンマッチとアクティブパターンはかっこいいif文以上の価値があるので積極的に使っていきたいですね。
Clojureで使って便利なマクロたち: .. doto ->> ->
この記事は Clojrure Advent Calendar 2011の参加記事です
(この記事はTokyo.clj#15で紹介した内容と同じです)
Clojureは従来の他のLisp系言語よりカッコを減らそうとしていたり、
オブジェクト指向ライブラリとの親和性を高めるための工夫が随所に見られます。
そういうものの中で今回はClojure特有の便利なマクロを4つ紹介します。
clojure.core/..
System.getProperties().get("os.name")
のようなメソッドチェーンを書くときに便利なマクロです。
.. を使わない場合
(.toLowerCase (.get (System/getProperties) "os.name"))
- 入れ子怖い
- メソッドと引数がはなればなれになる
- 処理の順番に読めない
.. を使う場合
(.. System getProperties (get "os.name") toLowerCase)
- 入れ子にならない
- メソッドと引数がセットで保たれる
- 処理の順番に読める
実際にはこの例はあまりイケてなくて、
(.toLowerCase (System/getProperty "os.name"))
と書く方が良いです。
使用例
(defn getCurrentDir [] "get working directory" (.. (java.io.File. ".") getAbsoluteFile getParent))
……この例もあまりイケてないですね。
(System/getProperty "user.dir")
と書く方が良いです。
使いどころだけわかっていただければと思います。
clojure.core/doto
JPanel panel = new MyPanel();
panel.setFocusable(focusable);
panel.setForeground(fgcolor);
panel.setBackground(bgcolor);
panel.addKeyListener(panel);
panel.addConpomentListener(panel);
のようなことを書くときに便利なマクロです。
doto を使わない場合
(let [h (java.util.HashMap.)] (.put h "a" 1) (.put h "b" 2) (.put h "c" 3) h) ;; => #<HashMap {b=2, c=3, a=1}>
- 一時変数に名前をつける必要がある
- 第二引数に一時変数が来る同じパターンの繰り返し
- 最後に値を返す必要がある
- 値を返さない式が並んでいる……副作用怖い……
doto を使う場合
(doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2) (.put "c" 3)) ;; => #<HashMap {b=2, c=3, a=1}>
- 第二引数に同じものが来るパターンを抽象化する
- 一時変数に名前をつけなくて良い
- 第一引数のオブジェクトが値として返る
- "doto" が副作用のマーカーになる
clojure.core/->>
関数呼出の入れ子を(処理の積み重ね)を平坦化するマクロです。
(cons 'd (cons 'c (cons 'b (cons 'a ())))) ;; => (d c b a)
のような、最後の引数に前の処理の結果が来るパターンを
(->> () (cons 'a) (cons 'b) (cons 'c) (cons 'd)) ;; => (d c b a)
のように書くことができます。
このマクロを使えば入れ子の段数が増えませんし、処理が上から順番に読めるようになります。
clojure.core/->
関数呼出の入れ子を平坦化するマクロです。
(conj (conj (conj (conj () 'a) 'b) 'c) 'd) ;; => (d c b a)
のような、第一引数に前の処理の結果が来るパターンを
(-> () (conj 'a) (conj 'b) (conj 'c) (conj 'd))
のように書くことができます。
doto と似ていますが、dotoでは結果が捨てられるのに対して、
- > は 前の処理の結果が次の処理に渡るところが違います。
余談
ところで、4clojure をやっていると無名関数をよく作る羽目になります。
- > と ->> はよく使うのですが、最初の引数をパラメータ化して関数として使うことが多くなってきます。
すると関数を返すバージョンをデフォルトで用意しておいて欲しくなってきました。
構文上の変更もすぐ実装できるのがLispのいいところ。ためしに実装して使ってみることにしました。
(defmacro => [& body] "same as #(-> % body ...)" `(fn [arg#] (-> arg# ~@body))) (defmacro =>> [& body] "same as #(->> % body ...)" `(fn [arg#] (->> arg# ~@body)))
作ってみたけど実プログラムではあまり使いどころがありませんでした。
こんなゴルフのためだけのマクロなんてデフォルトで入ってなくて良かったです。
MacPorts の Emacs.app に apel と ddskk をインストールする
apel のインストール
最新のソースコードをとってきて展開して、
# 何をするか確認 make EMACS=/Applications/MacPorts/Emacs.app/Contents/MacOS/Emacs what-where # インストール sudo make EMACS=/Applications/MacPorts/Emacs.app/Contents/MacOS/Emacs install
ddskk のインストール
そのままだと、infoとチュートリアル用のデータがEmacs.appの外に出てしまうので、Emacs.appの中にインストールされるように設定しました。
最新のソースコードをとってきて展開して、SKK-CFGを編集
(setq SKK_DATADIR "/Applications/MacPorts/Emacs.app/Contents/Resources/share/skk") (setq SKK_INFODIR "/Applications/MacPorts/Emacs.app/Contents/Resources/info")
あとはapelと同様
# 何をするか確認 make EMACS=/Applications/MacPorts/Emacs.app/Contents/MacOS/Emacs what-where # インストール sudo make EMACS=/Applications/MacPorts/Emacs.app/Contents/MacOS/Emacs install
MacPorts の emacs-app でフルスクリーンする
追記:emacs-app 23.2_1 からはフルスクリーンパッチが含まれるようになったため、この作業は必要なくなりました。
Mac で Cocoa Emacs を使うには、 MacPorts を使って emacs-app をインストールするのが一番手っ取り早いです。
Cocoa Emacs にはフルスクリーンにする機能がないのですが、幸いなことに typester さんがフルスクリーン機能を実装してくれているので、これを MacPorts で使ってみました。
以下手順です。
パッチを作ります。
emacs-appがemacsのリポジトリのどの時点なのかわからなかったので、
現時点のmasterを使ってパッチを作りました。
↓できたものがこれです
http://gist.github.com/585327
作る手順はこんな感じです
git clone git://git.savannah.gnu.org/emacs.git git remote add typester git://github.com/typester/emacs.git git fetch typester git merge typester/future/fullscreen git diff head~1.. > fullscreen.patch
emacs-appにパッチを当ててビルドします
gcc4系と相性が悪いらしく、gccではうまくビルドできなかったので、clangを使ってビルドしました。
まず emacs-app をビルドするのに必要なファイルを展開します。
sudo port extract emacs-app cd /opt/local/var/macports/build/_opt_local_var_macports_sources_rsync.macports.org_release_ports_aqua_emacs-app/work/emacs-23.2/
パッチを当てます
sudo patch -p1 < fullscreen.patch
コンパイラをclangに設定してインストールします
sudo port install emacs-app configure.compiler=clang
成功すれば、フルスクリーン機能のついた /Applications/MacPorts/Emacs.app ができているはずです。
typesterさんとEmacs開発者に感謝しつつ、Happy hacking!
leiningen の読み方は ライニンゲン
leiningen の FAQ (http://github.com/technomancy/leiningen) によると「LINE-ing-en と読むんじゃねーの」書いてあります。
これはカタカナにするとライニンゲンでしょう。
ちなみにドイツの地名の Leiningen も、カタカナではライニンゲンと書くのが一般的みたいです。
leiningen で clojure-1.2.0 を使う
clojure-1.2.0 では遅延シーケンスの扱いが改善されていて、時間がかかる処理を伴うシーケンスでも期待通りに動いてくれます。
そこで、 leiningen で clojure-1.2.0 を使うための project.clj のサンプルです。
(defproject your-project-name "your-project-version" :description "description of your project" :dependencies [[org.clojure/clojure "1.2.0-master-SNAPSHOT"] [org.clojure/clojure-contrib "1.2.0-SNAPSHOT"] ...] :dev-dependencies [[leiningen/lein-swank "1.2.0-SNAPSHOT"] ...] )
... のところは必要に応じて増やすなどしてねという意味です。
Clojureハッカソン
やりましょう
http://atnd.org/events/3729
日程:4/10
場所:未定