日曜プログラミング

休日趣味でやってるプログラミング関連記事をダラダラと

codox を使った GitHub へのもう少し手抜きな API doc 公開方法メモ

GitHubAPI doc を deploy する方法としては既に codox の HowTo としてもあるんだが、ここではもう少し手抜きな方法をメモしておく。GitHub Pages となるリポジトリは既に作っているものとする。

  1. lein doc してできた doc フォルダを普通に git commit&push
  2. codox でドキュメント作ってるプロジェクトの GitHub リモートリポジトリで gh-pages の名前のブランチを切る
    • どこでやるかは公式Create a gh-pages branch参照
  3. 10 分ほど待つ
  4. http://your-account.github.io/your-project/doc/ にアクセス

後はプロジェクトリポジトリの README.md とか適当な所にリンクを張れば終了。

試してみた例

これだけ。API doc の URL がプロジェクト名直下でないとヤダとかでなければ Deploying to GitHub Pages に書かれているようなコマンドライン打つ必要が無い分だけちょっと楽。

git とコマンドライン少し詳しければこの手順取らなくてもバッチなりシェルスクリプトなりで 対応可能かもしれんけどまあ一つの方法として参考になれば。

cider を 0.8.1 にアップデートしてみた

cider が 0.8.1 になってた のでアップデートしようとしたが割と手間取ったのでその手順をメモしておく。

今回は 0.7.0 の時と違い単なるインストール記事で使用感までは書いてない。 個人的に色々気になるのはあるけども今回は 元Changelog。 を見てもらうと言う事で。

手順概要

cider-nreplのアップデート

  • profiles.clj の [cider/cider-nrepl "0.7.0"] 記述を [cider/cider-nrepl "0.8.1"] に変更
  • コマンドラインで適当な leiningen プロジェクト下に移動して lein deps

el-getレシピの追加・変更

以下内容の queue.rcp をローカルレシピパスに追加する。

(:name queue
 :description "Queue data structure"
 :type elpa)

ローカルレシピパスの cider.rcp を以下のように変更する。

(:name cider
       :description "CIDER is a Clojure IDE and REPL."
       :type github
       :pkgname "clojure-emacs/cider"
       :checkout "14a7a7f42af5c8f4945920a03c9e2d7855976061" ; 0.8.1
       :depends (dash queue clojure-mode pkg-info))

:checkout に 0.8.1 の commit ハッシュ識別子?に相当する文字列追加。 ハッシュ識別子自体は github の releases を見ればあるのでそこから持ってきた。 :checkout を入れないと repl 起動時 cider 側が 0.8.2-SNAPSHOT だよと Warning が出る。

この辺りはel-get 公式へ既に merge されてるっぽくて、 多分こちらの環境の関係で通常手順で el-get をインストールしてないのが影響している気がするがちゃんとは調べてない。

el-get各パッケージアップデート

  • M-x el-get-install RET queue
  • M-x el-get-update RET cider
  • M-x el-get-update RET clojure-mode

repl起動確認

適当な leiningen プロジェクトの project.clj 開いて C-c M-j として repl に 0.8.1 と出てれば OK

所要時間: 2h

MELPA リポジトリを追加すれば良さそうなのは見たんだけど上手く行かなかったのでこちらは割愛。

もし、proxy 内で使ってて通常(ってあるのか知らんけど)手順で上手く行かなかった人は 参考になるかもと思い記事にした。

とりあえずはこのまま使ってみる。仮に 0.8.1 がまだまだ不安定だったとしても、今回のアップデート手順を同じようにしてダウングレードもできそうなので。

グチ

Github とかの俺の知識不足もあるんだろうけどやっぱめんどくさいよなあ。 主に今回引っかかったのは 3 つ。

clojure-mode アップデート忘れ

leiningen の感覚で依存してるのもアップデートしてくれるのかと思いきやしてくれず、 最初に cider-jackin した時何かヘンなエラー出た。そういやレシピファイルの :depends には 確かにバージョン指定なかったけどさ。

cider と nrepl-cider のバージョンミスマッチ

一つ前のを解消した後に cider-jackin すると、cider 側が 0.8.2-SNAPSHOT だよと Warning が出てた。 まあこれは解消せずとも使えそうな気はしたんだけど何となく気持ち悪かったので。

:checkout 調べるのに時間かかった。el-get-custom.el に version or checksum 的な値を 指定できるような事が書いてあったので、最初試しに "0.8.1" としてみたがダメだった。

今は Cask ってのがベター?

上二つが上手く行かずググってた時に見つけた情報。あーまた別のパッケージマネージャ的なもの? 日付的に割と最近乗り換えたというのを見て気にはなったものの今回は解決できたのでこちらを 試すのは保留した。

日本語キーワード

久しぶりの更新。小ネタだけど。

Clojure で日本語 Keyword は避けた方が良さそう。

:あとか:いとか単純なものだと普通に使えるんだけど アルファベットや漢字と混合したものだと関数のプロセスによっちゃ キーはあるのに値が取れない事がある。

取れなかったキーだけの単純マップデータ作って検索した場合は取れる。 しょうがないので全てアルファベットの Keyword に置き換えるとすんなり思い通りに動作した。

再現条件が良く分からないので悪いけどサンプルは載せられない。 まあ日本語 Keyword でどうこうしようと言う人はまずいないとは思うが。

Clojure のちょい便利マクロ

複数のシーケンスを対象に先頭からそれぞれ要素を取って処理したい時どうするか。 map が順当なんだけど渡す処理がごく簡単な場合ならまだしも ちょっと複雑になってくると少し見辛くなる。

例えばこんなの。

(map
  (fn [avar bvar cvar]
    (->> (foo-fn avar)
         (bar-fn bvar)
         (baz-fn cvar)))
    coll-a coll-b coll-c)

;; リーダマクロで多少簡略化できるが・・・
(map
 #(->> (foo-fn %1)
       (bar-fn %2)
       (baz-fn %3))
 coll-a coll-b coll-c)

これは結局渡すコレクションが下に来ているのが見辛さの原因になってる。 だったら上に持ってくる doseq でいいんじゃないかと最初思うがそうもいかない。

(doseq [a [1 2 3]
        b [4 5 6]]
  (println [a b]))
[1 4]
[1 5]
[1 6]
[2 4]
[2 5]
[2 6]
[3 4]
[3 5]
[3 6]
nil

複数シーケンスを渡した時の doseq は、全要素の組み合わせをたどる。 これはこれで良いのだが、各シーケンス要素を平行に取って処理したい時もしばしばある。

だったら最初の map に展開するようなマクロ作っちゃえと言う事でこんなん作った。

(defmacro domap
  "map 版 doseq。複数のシーケンスをバインドさせた時、全ての組み合わせを行うのではなく、
   map と同様に先頭から順に処理し、一番短い所で終了する。残りのアイテムは無視する。"
  [seq-exprs & body]
  `(doall (map (fn ~(vec (take-nth 2 seq-exprs))
                 ~@body)
               ~@(take-nth 2 (rest seq-exprs)))))

doall を入れておかないと REPL 上で副作用結果と map の結果シーケンスが入り混じって 表示される事になる。実際に返すシーケンスに副作用結果値が入るわけではないので それが気にならないなら doall は入れなくても構わない。lazy-seq じゃなくなってしまうしね。 結果シーケンスを捨てても良いなら doall の替わりに dorun という手もある。

使い方がこんな感じ。

(domap [i (range)
        j [3 4 5]]
  (println i j))
0 3
1 4
2 5
(nil nil nil)

ちょっとした小ネタでした。

Clojure で lazy-seq を生む関数についてのトラブルシューティング

Clojure で lazy-seq を生む関数と言うのは cheatsheetの Creating a Lazy Seq に書かれている関数の他に も良く使う所では map 関数なんかもそうだったりする。Programming Clojure を読んだ事があれば 何となく記憶してるかもしれない。

REPL で確かめてみると分かる。

(type (map inc [1 2 3]))
;=> clojure.lang.LazySeq

さて、今回改めて取り上げるのはこの lazy-seq、一部 NPE を誘発する状況があり、 その場合どうするかをメモしておこうと思う。

自分が発生した時の詳細な条件は掴みきれていないものの Java interop, nth 辺りを組み合わせて使っていた時に発生しており、 多分前者じゃないかと予想はしているが未検証。

この話で厄介なのは、REPL や println デバッグだと出ないという事。 REPL も println も lazy-seq を渡しても一度全て評価してから表示するためである。

その場しのぎ的な対応にはなるが、lazy-seq を生む関数なのかどうかは一旦忘れ、 NPE が発生したらシーケンス加工中に lazy-seq になってるのが原因かどうかを確認し そうであれば不格好だが doall とかかましていくように対処する事にした。 lazy-seq を全て避けた標準の代替関数を作るのもかったるいし。

これは機会があればどこで発生するのか改めて検証したい。

Clojure の名前空間とファイル名

Clojure では名前空間を切る時、通常は名前空間名と.cljを除いたファイル名を同じにするよう構成する事になっている。 また、名前空間をドットで区切るのはパス区切りと対応付けられる。

名前空間foo.bar だったら foo\bar.clj となる。

一方、lisp でのシンボル名は通常ハイフン(-)区切りが慣習になっている。 で、名前空間でもこの慣習に従ってハイフン付きで同じファイル名で保存後 require などと すると Java の命名可能文字列仕様の制限にぶち当たる。

例えば

(ns foo.bar-baz)

名前空間を定義し、ファイル名を foo\bar-baz.clj として

(require 'foo.bar-baz)

としても

CompilerException java.io.FileNotFoundException: Could not locate foo/bar_baz__init.class or foo/bar_baz.clj on classpath: , compiling:~

と例外が出る。この例外はメッセージにある通り、 ファイル名を foo\bar-baz.clj でなく foo\bar_baz.clj とすれば解決する。

Java 自体の命名可能文字列の話は こちら が分かりやすい。

名前空間下にある関数名の場合はコンパイラで変換してくれるのか気にしなくてもいいだけに 個人的には若干気持ち悪い。解決策あるからいいっちゃいいんだけど。

これ あんまり Java 知らない Clojure 使いは一度は引っかかるんじゃないのかなあ。 そういや Clojure に最初から入っているライブラリの名前空間名にもハイフン付きって見た事ないな。

Clojure の SQL DSL の一つ sqlingvo なかなかイイですよ

[2014-07-22 追記] table 別名がデフォルトで AS なしに修正された事を示すリンクを追記

Clojure 界隈では、SQL DSL の選択肢が結構前からいくつか存在する。初期からある KormaHoneySQL そして今は ClojureJDBC ラッパーのデファクトスタンダートとなってる clojure.java.jdbc でもごく簡易的な DSL を備えている

今回はその中でも sqlingvo を使ってみたのでその感想を書いてみる。

今回はあくまで感想のみ。他ライブラリとの比較は使ってないのでできないし、 インストールやごく基本的な使い方に関しても公式を見れば大体分かると思うので書かない。

良いと思った所

WITH 句、CASE 式対応

個人的に決め手になったのはこれ。特に WITH 句はこいつが対応してるとアプリ側でなんちゃって View のようなものを共通で持つ事が簡単になる*1

他にも DELETE, INSERT, UPDATE はじめ、実用するに必要なもの一通りが揃っている感じ。

CASE 式もクエリで取る段階で判定して処理しとけるならしときたいよね、と言う場面はちょいちょ いあるのでこれは嬉しい。勿論 CASE 無しで取っておいてアプリ側でループで条件判断しながらぶん 回せば同じ事はできるけど。けどなあ。

素の SQL にかなり近い

例えばこういうの。

(select [1 2 3] (from :dual))

SQL 嫌いじゃないんでそんな Clojure っぽくマップデータ化とかする必要ないと思うんです。

役割としては sqlingvo の SQL DSLclojure.java.jdbc で扱い易い形に変換するだけ

clojure.java.jdbc で扱い易い形と言うのはこういうの↓

(sql (select [1] (from :dual)))
;=> ["SELECT 1 FROM \"dual\""]

ベクタで先頭要素がパラメータ付き SQL 文字列、残り要素がパラメータに渡す値と言う形のやつ。

つまり実際に DB からデータを取ってくるには clojure.java.jdbc も一緒に使う必要がある。 だけどこれまでも clojure.java.jdbc 使って生 SQL 投げていた自分にとっては殆ど問題にならない。

SQLClojure 上で扱いやすく書ける機能のみの提供と言うのかな。

これは言い替えると実際のスキーマに存在しているかどうかは sqlingvo では全くチェックしないの で注意するとしたらそれくらいか。

微妙だと思った所

Oracle との親和性が若干悪い

どっちかっつーとこの 2 つは Oracle 対応しろよと言う感じなのかな。標準仕様どこで読めるか分 からんから何とも言えんけど。10 以降だと対応してるのかも?

SQL 文字列展開時に DB オブジェクト名がダブルクォーテーションで括られるのに Oracle9i が対応してない

最初の変換例にあるような形そのままのは Oracle(9i) では許しちゃくれません。 なので Oracle の時はダブルクォーテーションを外すヘルパー関数をかましてやる必要がある。

FROM 句でのテーブル別名で AS を入れるが Oracle9i が(ry

昔ながらの (+) で表結合するスタイルの人はこれでもう選択肢から外れるのかな。 ただこれも JOIN で行う結合においてもサブクエリが使えないという制限がかかってしまうことになる。

[2014-07-22 追記] 0.5.17 以降は table 別名はデフォルトで AS 無しに修正されたようです

ちょっとマクロシンタックスが多くない?

結構量がある テストコード は sqlingvo の場合書き方のサンプルにもなってて、これ眺めてると分かるが、それなりに複雑なク エリになるとクォート・アンクォート・バッククォートも併用しないとちゃんと変換してくれない。

何かマクロを書いているような気分になってきてなんだかなあ、と言うのはちょっとあるw

HAVING 句のサポート

できれば欲しい。使わない書き方にするのも割と簡単だけども。

最後に

微妙と思った点などで挙げたように一部制限やクセはあるものの、クエリの一部を取り出して使い回 したいと思っていた人には結構オススメ。SQL の弱点と個人的には思っている分割粒度がどうしてもデ カくなるのを上手く補っているライブラリじゃないかなと。

*1:共通で持てる SQL 文字列を分割してるようなもの なので実際データ取る時は毎度リクエストを投げるが