「Clojureシンタックスハイライター開発から考えるこれからのLispに必要なもの」を発表しました

7/27に開かれたLisp Meetup #30で「Clojureシンタックスハイライター開発から考えるこれからのLispに必要なもの」というタイトルで話してきました。

内容としては、去年のShibuya.lispのテクニカルトークで話した内容を重点をズラして焼き直したものです。終わった後にいくつか意見をいただきましたが、絶対数は多くないのでどう受け止められているのかはちょっと気になるところです。

「これからのLispに必要なもの」とタイトルにはあるものの、具体的に「これが必要だ」と言えてないのが残念な感じですが。。。この発表でいいたかったのは、「LispコードをCASEツールで解析できる対象にしていきませんか」という提案です。

ICSE勉強会なんかの話を聞いていると、他の言語(特にJavaとか)はコードの解析手法なんかがガンガン積み上げられていってて、開発環境がどんどん便利になっているように思います。一方でLispは、強力なマクロがあることがコードを解析する面では大きな障壁になっていて、このあたりの話がそんなに進んでいないように感じられます。Lispではコード中のどこが式なのかを(コンパイラの外から)判別することすら一般的には難しいのです。

もちろん、マクロ展開時にあらゆる関数が呼び出せてしまう以上、完全な静的解析をするのは不可能ですが、その中でも状況が年を追うごとに改善してきているという実感がさっぱりないのが寂しく感じます。Scheme方面の、マクロをシンタックスオブジェクトからシンタックスオブジェクトへの関数として定義する方向性はひとつの解なのかなぁと個人的には思っています。伝統的なLispマクロを扱う言語についてはどうでしょうか?

リンク

一応、シンタックスハイライターと解析器のプロジェクトのリンクも貼っておきます。

解析器についてはかろうじてインストールして使ってみることができる状態です。シンタックスハイライターとLeiningenプラグインについてはもうしばしお待ちを。

"Contributing to Clojure"の翻訳記事を公開しました

Contributing to ClojureClojureにコントリビュートする際に必要な手続きやワークフローについてAlex Millerが説明した記事です。

元記事自体は、Clojureの開発に関する広範な情報を提供してくれていて、Clojureの開発に関わりたいと思う人にとって非常に有益な記事ですが、個人的にこの翻訳記事を公開することで特に周知したかったのは以下の2点です。

  • Clojureの開発はpull requestベースではない
  • ClojureのコントリビュータにはClojure CAに署名するだけでなれる(名目上は)

ClojureリポジトリGitHubホスティングされてますが、PRは受けつけていません。不具合を見つけたからといって、いきなりPRを送っても見てもらえることはありません。Clojureの課題管理にはJIRAが使われているので、問題の報告はそちらにどうぞ。

また、ClojureのコントリビュータになるにはCAに署名する必要があります。これは権利関係をクリアにするための措置ということになってます。署名を送る必要はあるものの、逆にいうとCAを送るだけで実質的な貢献がなくともコントリビュータになれるのでお手軽です :P 皆さんこぞってClojureコントリビュータになりましょー。

おわりに

最近ガンガンClojureの情報をアウトプットしているあやぴーさん(id:ayato0211)が先日、Clojure界隈は「英語出来る人と英語出来ない人の間を繋ぐアダプタ的な層になる人が少ない」ということを書いていたので、今後も微力ながらこういうことがやっていけたらなぁと思います(やるとは言ってない)。

ニャンパス株式会社に転職しました

先月で前職を辞め、今週からニャンパスで働き始めたので報告します。


前職

前職では、組込み業界で働いてました。とはいっても、組込みソフトウェアの開発自体には携わってなくて、主に組込みソフトウェア開発のための開発環境の開発をやってました。
詳しくは書きませんが、会社が求める能力・スキル・ワークライフバランスと、自分が目指したいものが違うことから転職に踏み切りました。いわゆる「音楽性の違い」というやつですね。12月半ばに最終出社を迎え、1月いっぱいまでは有休消化期間でした。

転職先:ニャンパス

2月からはニャンパスで働いています。ご存じの方もいるかもしれませんが、一昨年の秋頃にアニメ「のんのんびより」の中に出てくるセリフのおかげ?で話題にもなった会社です。
ニャンパスは、Clojureをメインの開発言語にしている日本で数少ない会社で、BaaSサービスのbaasdayや、その他WebサービスClojureで開発したりしています。ニャンパスでの開発についての話は、また追々発信していきたいと思います。

転職の経緯

もともとClojureを趣味で触っていて、(ほとんど名ばかりですが)コントリビュータもやっていたので、仕事でClojureを使っているというニャンパスには興味がありました。
のんのんびよりの騒動でニャンパスがにわかに話題になり、代表の登尾さんがネット記事にインタビューされる流れがありました。その流れに乗じて、登尾さんがClojure開発者をゆるく募集していたので、話を聞きたいと僕からリプライした感じです。

で、後日登尾さんと会って話をしたんですが、その日のうちにはもちろん「じゃあ、さっそく転職だ!」という話にはならないので、その後も登尾さんが主催するTokyo.cljに定期的に参加しに行くついでで継続的にやりとりを続けて、最終的に転職というところまでこぎ着けることができました。
振り返ってみると、にゃんぱす騒動がなければこういう展開にはならなかったかもしれないので、人のめぐり合わせなんて何がきっかけになるか分からないものです。

さいごに

まったく違う業界への転向なので、いろいろ勝手が分からないこともありますが、これからニャンパスでお金をもらうプロのニャンパスマンとして頑張っていきたいと思います。
また、昨年12月からニャンパスが運営するコワーキングスペースHaLake(ハレイク)がオープンしました。越谷レイクタウンにあるオサレな感じのコワーキングスペースです*1。ニャンパスのオフィスとしても使っているので、興味がある方はぜひお越し下さい。

冒頭の写真は、転職・退職エントリで有名な某店ですが、ちょうど行ったときに休みだったので店先で写真だけ撮って帰ったときのものです。ご査収下さい。

最後になりましたが、ウィッシュリストを公開してます(→こちら)。もしよかったらポチっていただけると幸いです。

こちらからは以上です。

*1:なお、のんのんびよりの登場人物こまちゃん・なっつん姉妹の苗字は「越谷」ですが、HaLakeの所在地が越谷レイクタウンなのも偶然の一致によるもので意図したものではありません。念のため。

tools.analyzerとtools.emitterを組み合わせてキミだけの最強Clojure処理系を作ろう

(このエントリは、Clojure Advent Calendar 2014 21日目、および Clojure Contrib Library Advent Calendar 2013 24日目の記事です。)

今回はClojureのcontribライブラリであるtools.analyzerとtools.emitterを使ってカスタマイズしたClojure処理系を作ってみます。

tools.analyzer/tools.emitterとは

tools.analyzerとtools.emitterはClojureのセルフホスティングコンパイラ、つまりClojureで書かれたClojureコンパイラを構成するライブラリ群です。以前からCinC(Clojure in Clojure)という名前だけがあったプロジェクトが、ようやく具体的にcontribライブラリという形で開発が進められているという状況です。これらのライブラリは、Clojureのコア開発陣であるCognitectから出資を受けたり、Typed Clojureクラウドファンディングで集めた資金の一部が使われるなど、開発に非常に期待がかけられていて、将来的にClojure標準の次期コンパイラになる実装だと考えられます。

tools.analyzer/tools.emitterの大雑把な構成はこうです。 tools.analyzerとtools.emitterは抽象構文木(AST)を中心にして、ASTを作るまでの構文解析部分をtools.analyzerが、ASTを作った後の(ターゲット言語での)コード生成部分をtools.emitterが担当します。

tools.analyzer/tools.emitterからなるこの枠組みは、実際にはClojureだけでなく、ClojureScriptも対象にしています。ClojureとClojureScriptは言語としても微妙に違いますし、ターゲット言語もかたやJVMバイトコード、かたやJavaScriptと異なっています。
このような状況に対応するために、

  • ASTの拡張可能な標準フォーマットを規定
  • ソース言語の共通部分についてはtools.analyzerが対応
  • ソース言語固有の部分はanalyzerの拡張ポイントで対応
  • ターゲット言語ごとにemitterを実装

という構成になっています。
これをさきほどの図に反映させると、こんな感じになるでしょうか*1

ClojureおよびClojureScriptの解析には、それぞれtools.analyzer.jvmおよびtools.analyzer.js(以下、t.a.jvmおよびt.a.js)という個別のライブラリを使います。Clojureのコード生成にはtools.emitter.jvm(以下t.e.jvm)というライブラリを使うことができます。現時点では、ClojureScript用のJavaScriptコード生成に使えるemitterの実装はありません。

インストール

tools.analyzer/tools.emitterをLeiningenから使うには、以下を依存ライブラリとして追加します。各ライブラリのバージョンは2014/12/20時点の最新版です。

[org.clojure/tools.analyzer "0.6.4"]
[org.clojure/tools.analyzer.jvm "0.6.5"]
[org.clojure/tools.analyzer.js "0.1.0-SNAPSHOT"]
[org.clojure/tools.emitter.jvm "0.1.0-SNAPSHOT"]

これらのライブラリ間には、t.a.jvmおよびt.a.jsがtools.analyzerに依存し、t.e.jvmがtools.analyzerとt.a.jvmに依存する、という依存関係があります。どのライブラリも開発途上でまだまだ不安定で、互換性のないバージョン同士を一緒に使うとうまく動かないこともあります。そのため、各ライブラリのバージョンを個別に指定するのではなく、依存するライブラリのバージョンをLeiningenに解決してもらった方が安全でしょう。

analyzer/emitterの使い方

analyzerとemitterの使い方を見てみましょう。ここではtools.analyzer.jvmとtools.emitter.jvmについて見てみます。

tools.analyzer.jvmの使い方

tools.analyzer.jvmのエントリポイントの1つはanalyze関数です。analyzeは解析するフォーム1つを引数にとり、解析結果を戻り値として返します。

user=> (require '[clojure.tools.analyzer.jvm :as a])
nil
user=> (set! *print-level* 2)   ;; 解析結果は巨大なマップになるので表示を省略
2
user=> (a/analyze '(let [x 42] x))
{:children [:bindings :body], :bindings [#], :op :let, :env {:file "/private/var/folders/zr/_mpf274n7mn0_5fyx84ypb280000gn/T/form-init3642608216430177312.clj", :line 1, :column 13, :context :ctx/expr, :locals #, :ns user}, :o-tag long, :top-level true, :form (let* # x), :tag long, :body {:o-tag long, :tag long, :body? true, :op :do, :env #, :form #, :statements #, :ret #, :children #}, :raw-forms (#)}
user=>

tools.analyzer.jvmには他にも、名前空間全体を一括して解析するanalyze-nsや、解析した後に解析した結果をevalするanalyze+evalなどが用意されています。詳しくはリファレンスを参照して下さい。

tools.emitter.jvmの使い方

tools.emitter.jvmはevalとloadを提供します。どちらもClojure標準にある関数を、tools.analyzer/tools.emitterのツールチェーンによる実装に置き換えたもので、標準のものと同じように使うことができます。

user=> (require '[clojure.tools.emitter.jvm :as e])
nil
user=> (e/eval '(let [x 42] x))
42
user=> (e/load "foobar/core")
nil
user=>

analyzer/emitterの拡張

さきほど述べたとおり、analyzer/emitterの枠組みは拡張することを想定して作られています。ここではオリジナルのanalyzer/emitterの作り方について概要をサラッと説明します。

analyzerを作る

tools.analyzerには、各analyzerが実装するべき拡張ポイントが用意されています。自分でanalyzerを実装する場合には、この拡張ポイントを実装する必要があります。

  • macroexpand-1: マクロ展開処理
  • parse: 特殊形式をパースするマルチメソッド
  • create-var: Varの生成処理
  • var?: オブジェクトがVarかどうかをテストする述語

Varに関する関数が拡張ポイントになっているのは奇妙に感じるかもしれませんが、これはClojureとClojureScriptという異なるプラットフォーム上ではVarの扱いが変わることに起因します。

また、analyzeから生成されたASTに対して実行する処理を「パス」として提供することもできます。「パス」は特殊形式単位よりも広い範囲の構文木を見ながら進める必要がある処理を記述するために用意されています。tools.analyzerは「パス」処理用のスケジューラを持っていて、構文木全体をなめる回数をなるべく少なくするように動作します。

emitterを作る

analyzerと異なり、emitterには決まった拡張ポイントはありません。基本的な構成としては、emitterがanalyzerを呼び出し、得られたASTを基にコード生成処理をする、という感じになります。

「パス」処理はemitterのコード生成に必要な処理を記述するのにも使えます。形式もanalyzerの場合と違いはありません。

「ぼくのかんがえたさいきょうのClojure

以上を踏まえたうえで、tools.analyzer/tools.emitterの枠組みを使って、カスタマイズしたClojure処理系を作ってみましょう。ここではtools.analyzer.jvmおよびtools.emitter.jvmをベースに考えます。

今回作るのは以下のような、インラインアセンブリとしてJVMバイトコードを記述できるようにする特殊形式を追加したClojureです。

(asm <インストラクション1>
     ...
     <インストラクションn>)

コンパイル結果のバイトコードには、asm特殊形式のボディにS式として書かれたJVMバイトコードのインストラクション<インストラクション1>, ..., <インストラクションn>がこの順に出力されます。

実装してみる

asm特殊形式を扱えるようなClojure処理系を作ってみましょう。

省力化のために、今回はtools.analyzer.jvmとtools.emitter.jvmの一部を上書きすることでanalyzer/emitterの実装に代えます。

実装の方針としては、analyzeした結果としてできるASTにasm特殊形式に渡されたインストラクション列を保持し、コード生成時にその保持したインストラクション列をそのままバイトコードに出力するようにします。

tools.emitter.jvmは内部にJVMバイトコード(の一部)をS式で記述できる機能を持っているので、それを流用してインストラクションをS式で書けるようにします。

細かいことをいろいろ省くと、この程度のコードで上記の機能を実現できます。

(ns prime-clojure.core
  (:require [clojure.tools.analyzer.jvm :refer [parse specials]]
            [clojure.tools.emitter.jvm.emit :refer [-emit]]))

(alter-var-root #'specials conj 'asm)

(defmethod parse 'asm [form env]
  {:op :asm
   :env env
   :form form
   :children []
   :insns (vec (for [[op & args] (rest form)]
                 `[~(keyword (name op)) ~@args]))})

(defmethod -emit :asm [{:keys [insns]} frame]
  insns)

実行例は以下のとおりです。

user=> (require 'prime-clojure.core)
nil
user=> (require '[clojure.tools.emitter.jvm :as e])
nil
user=>
(e/eval
  '(asm (push "hoge")
        (push "fuga")
        (invoke-virtual [java.lang.String/concat String] java.lang.String)))
"hogefuga"
user=> 

tools.emitter.jvmが持っているS式バイトコードがカバーするインストラクションの割合が思った以上に低いのでできることはだいぶ制限されていますが、同様の考えにしたがって自前で補完していけばカバー率を上げることができます(今回は時間が足りなかったので割愛)。

まとめ

今回は、tools.analyzer/tools.emitterの紹介と、それらを使ったClojure処理系の自作方法について見てきました。

上でも触れたように、tools.analyzer/tools.emitterはまだまだ開発途上で、tools.emitter.jvmが提供するevalはClojure標準のものより5~8倍遅いようなので、今すぐに現行コンパイラを置き換える存在になれるわけではなさそうです。しかし、Clojureで書かれている分、Javaで書かれた現行コンパイラよりも変更が容易なため、いろいろなコンパイル技術を試すプロトタイピング環境としては今からでも期待できるでしょう。今後の開発が進んでいくのが楽しみです。

明日はnobkzさんの番です。乞うご期待!

*1:ここでは説明のためにtools.emitterという名前をtools.emitter.*の総称として使っていましたが、実際にはtools.emitterというライブラリはありませんので注意して下さい

女子力botをもっとイケてるClojureっぽく

(このエントリは、しょぼちむアドベントカレンダー 15日目の記事です)

はじめに

Clojureアイドルことしょぼちむさんが先日開催されたClojure夜会で、Clojureで書かれた女子力botなるものについて発表していました。Clojure歴3週間とのことでしたが、そうとは思えないほどよく書けています。しかし、Clojureのidiomaticな書き方の中には、時間を書けて慣れていかないとなかなかすんなり書くことが難しいものもあります。

そこで、今回は女子力botを題材によりClojureっぽいイケてる書き方を紹介します。

書き換えてみよう

今回ピックアップする項目は以下のとおりです。

  • clojure-contribは使わないようにしよう
  • シーケンス処理に持ち込もう
  • *-in関数を使おう
  • 状態をloopで持ち回そう
  • 参照を公開しないようにしよう

(ここで取り上げていないすべての変更を含めた最終版はこちらから確認できます。)
それでは順番に見て行きましょう。

clojure-contribは使わないようにしよう

まず最初に注目するのはこちら。

core.clj

(ns jyoshiryoku-bot.core
  (:import [twitter4j TwitterFactory Twitter Paging])
  (:require [clojure.contrib.str-utils :as str-utils]
            [clojure.java.io :as io]
            [jyoshiryoku-bot.kaiseki :as kaiseki])
  (:gen-class))

core.clj

(str-utils/re-sub #"(@.*?\s)+" "" (.getText mention))

3行目でclojure.contrib.str-utilsがrequireされています。clojure.contrib.str-utilsというのは、いわゆるレガシーcontribあるいはモノリシックcontribと呼ばれるライブラリの一部です。
レガシーcontribというのは、Clojure contribライブラリの過去の形態で、Clojure標準には含まれない準標準的な機能をひとまとまりのJARとして配布していたものです。レガシーcontribはすでにdeprecatedな扱いになっていて、保守もされていません。
レガシーcontribの多くの機能はClojure標準に取り入れられているか、同様の機能を提供するモジュラーcontribが用意されているので、特別な理由がなければそれらを使うようにすれば問題ありません。ここで使われているstr-utilsも、多くの機能が現在のClojure標準におけるclojure.stringから提供されていて、str-utils/re-subにはclojure.string/replaceが相当します。
「レガシーcontribのこのネームスペースに相当する機能はどこに行ったんだろう?」と疑問に思った場合にはこちらのドキュメントが役に立つかもしれません→Where Did Clojure.Contrib Go

*-in関数を使おう

つづいて注目するのは次のコードです。

kaiseki.clj

(defn inc-map-value [m k]
  (if (get m k)
    (update-in m [k] inc)
    (assoc m k 1)))

(defn register-word [m word1 word2]
  (let [word2-map (get m word1 {})]
    (assoc m word1 (inc-map-value word2-map word2))))

この関数register-wordは以下のように使います。

user=> (register-word {} "hoge" "fuga")
{"hoge" {"fuga" 1}}
user=> (register-word *1 "hoge" "fuga")
{"hoge" {"fuga" 2}}
user=> (register-word *1 "hoge" "piyo")
{"hoge" {"piyo" 1, "fuga" 2}}
user=> (register-word *1 "fuga" "piyo")
{"fuga" {"piyo" 1}, "hoge" {"piyo" 1, "fuga" 2}}
user=> 

register-wordは、ネストしたマップを使って、2つの単語が連続して出現する回数を管理します。Clojureではこのように、ネストしたマップやベクタを非破壊的に更新していくような使い方をよくします。
ネストを辿るたびにgetやassocを繰り返すのは煩雑です。そこで、Clojureはこういったネストしたデータを扱うのに便利な関数(get-in/assoc-in/update-in)を準備しています。

; ネストしたマップからキーを引く
user=> (get-in {:hoge {:fuga 42}} [:hoge :fuga])
42
; ネストしたマップに値をassocする
user=> (assoc-in {:hoge {:fuga 42}} [:hoge :piyo] 101)
{:hoge {:piyo 101, :fuga 42}}
; ネストしたマップの値を関数で更新する
user=> (update-in {:hoge {:fuga 42}} [:hoge :fuga] inc)
{:hoge {:fuga 43}}
user=> 

さきほどのregister-wordはupdate-inを使うと次のように書くことができます。

(defn register-word [m word1 word2]
  (update-in m [word1 word2] (fnil inc 0)))

シーケンス処理に持ち込もう

次はこちら。

kaiseki.clj

(defn load-text [file-name]
  (let [text (slurp file-name)
        tokens (tokenize text)]
    (reduce (fn [m [token1 token2]]
              (let [word1 (token-word token1)
                    word2 (token-word token2)]
                (if (= word1 "")
                  m
                  (register-word m word1 word2))))
            {} (partition 2 1 tokens))))

reduceに引数として渡しているfnの中身がやや複雑です。ここでやっているのは、

  • 隣り合う2つのトークンに対してtoken-word(空白の除去等をする)を適用し、
  • 最初のトークンが空でなければregister-wordで2つのトークンを登録する。それ以外の場合は何もしない

といった処理です。

これを整理してみると、以下のことが分かります。

  • token-wordはすべてのトークンに対して適用しているので、fnの中ではなくてreduceに渡すシーケンスにあらかじめmapしておけばいい
  • 隣り合うトークンの組からなるシーケンスから、最初のトークンが空の組をフィルターしておけばfnの中で条件分岐しなくて済む

このようにfnの中の処理を整理すると、複雑だった処理がより単純なシーケンス処理の組み合わせとして書き直すことができます。Clojureはシーケンスに対する便利な関数を非常にたくさん持っているので、複雑に絡んだ処理もシーケンス処理に持ち込むことで、組込み関数の組み合わせで書くことができるようになるかもしれません。
load-text関数を上の考えのしたがって書き直すとこのようになります。

(defn load-text [filename]
  (->> (tokenize (slurp filename))
       (map token-word)
       (partition 2 1)
       (remove #(= (first %) ""))
       (reduce #(apply register-word %1 %2) {})))

適宜スレッディングマクロ(->>)を使うことで、処理の見通しがさらによくなり、バグを埋め込みにくくなります。

状態をloopで持ち回そう

さらにこちら。

core.clj

(let [info (atom (mentionInfo))]
  (while true
    (if-not (= @info (mentionInfo))
      (do (reset! info (mentionInfo))
          (tweettimeline (str ".@" (:userName @info) " "
                              (kaiseki/create-sentence @kaiseki/*words* (searchword))))))
    (Thread/sleep (* 1000 60 2))))

atomを使って、繰り返しごとに変化する状態を管理しています。
よりidiomaticなClojureコードでは、繰り返しで変化する値をループで持ち回すことで状態を管理します。

(loop [old (mentionInfo)]
  (let [new (mentionInfo)]
    (when-not (= old new)
      (let [sentence (kaiseki/create-sentence @kaiseki/*words* (searchword))
            message (format ".@%s %s" (:userName info) sentence)]
        (tweettimeline message)))
      (Thread/sleep (* 1000 60 2))
      (recur new))))

ループで書くとコードが著しく複雑になるようなケースを除けば、上のように非破壊的に状態を更新していくやり方の方が好まれます。

参照を公開しないようにしよう

最後はやや設計に絡む話。

kaiseki.clj

(defn init [tweettxt]
  (dosync
    (ref-set *words*
             (load-text tweettxt))))

initの中では、ref型の*words*にロードしたデータを代入するようになっています。
これを利用する側は、initを呼び出した後に*words*を直接参照します。

core.clj

(defn -main []
  ...
  (kaiseki/init "tweet.txt")
  ...
  (kaiseki/create-sentence @kaiseki/*words* (searchword))))))
  ...)

Clojureには破壊的に変更できるatomやrefといった参照型が用意されています。参照型を使う場合には、「それがどこで変更されうるか」に気をつける必要があります。参照型を外部へ公開すると、外部で何の制約もなしに参照を変更される可能性があります。そのため、参照型をそのまま公開するのは極力避けた方がいいでしょう。

今回の場合には、initでロードしたデータを*words*に代入するのではなく、load-text関数をパブリックなインタフェースとし、ロードしたデータを保持することは利用する側の責任とすることで、参照型を公開するのを避けることができます。

(let [words (kaiseki/load-text "tweet.txt")]
  ...
  (kaiseki/create-sentence words (searchword))
  ...)

おわりに

今回は女子力botを題材によりClojureっぽい書き方について紹介しました。
もっと時間をかけて「じっくりとClojureのidiomaticな書き方を知りたい!」という場合は、Clojureの本に当たることをおすすめします。

これらの本は、Clojureらしい書き方を教えてくれるだけでなく、「なぜそう書くのか」についてもきちんと説明されているのでオススメです。

Clojure夜会でLT発表してきた

もう1週間以上経ってしまったけど、10/10に開かれたClojure夜会に参加してきた。

平日の夜から(しかも東京で)のイベントだったので最初は参加は難しいなぁと思っていたのだけど、休みがギリギリとれたので4日前くらいになって急遽参加することにした。

発表について

せっかくわざわざ東京まで行くのにただ話を聞いているだけというのももったいないなと思い、LT枠も余っている様子だったのでそこから急造でネタごしらえ。自分が最近やっていることでネタになりそうなおもしろい話が特になかったので、去年くらいからちょくちょく海外のClojureコミュニティの人たちのブログで話に挙がるStuart Sierra*1が提案するClojureワークフローについて紹介することにした。

当日の発表資料はこちら。

おおざっぱな内容は、REPL上でシステムを動かしつつ開発をするときに、名前空間のリロードによってシステムの状態が不整合を起こさないようにいくつかのライブラリの助けを借りて開発を進めるというもの。ある程度Clojureを触っている人なら、ClojureのREPL上でいろいろ試していると、なんだかよく分からないけどエラーが出てしまって、諦めてREPLを再起動するという経験はあると思う。そういう場合に、この発表で紹介したいくつかのtipsが役に立つかもしれない。

ただ、発表資料は5分のLT用に作ったものでいろいろと話を端折っているので、実践で本当に使おうと思うとこれだけでは不十分だとも思う。英語が苦にならなければ原典にあたることをオススメする。

Blog | My Clojure Workflow, Reloaded | Relevance
Stuart SierraがClojureワークフローについて書いたブログ記事
clojure/tools.namespace · GitHub
Clojureのリロード機能の強化版ライブラリ
stuartsierra/component · GitHub
システムを「コンポーネント」単位で構成するのを支援する簡易的なフレームワーク
Stuart Sierra - Components Just Enough Structure - YouTube
Stuart SierraがComponentライブラリについてClojure/West 2014で発表した動画

また、半年ほど前にPuppet Labsが発表したTrapperKeeperというフレームワークは、このClojureワークフローを下敷きにしていると言っているので、このトピックについて知っておくとTrapperKeeperの理解を深める助けにもなるかもしれない。

イベントについて

Clojure夜会というイベント自体の話をすると、まず人数が尋常ではなかった。当日の実際の参加人数については知らない*2けど、登録人数100人超えというのはTokyo.cljはおろか日本最大級だといわれているLisp系イベントShibuya.lispテクニカルトークのピーク時を凌ぐレベルだ。すごい。正直、Clojureだけでこれほどの人数を集められるとは微塵にも思ってなかったので登録人数がガンガン増えていく様子を見て何が起きてるのか分からなかった。

主催者陣側の話では、金曜日開催にしたのが功を奏したんじゃないかということだった。仕事帰りに立ち寄る方が、休日を犠牲にして参加するよりは参加しやすいということらしい。たしかに一理ありそうだけど、本当にそれだけなんだろうか。このあたりは、今後の参考のためにアンケートとるなりしてもうちょっと詳しく調べてもいいんじゃないか。

個人的な感覚としては、あれだけの規模のイベントだったわりにはTwitterのタイムラインの流量はそんなに多くなかったかなという印象があった。もしかしたら参加者の結構な割合が非Twitterユーザだったのかもしれない。Tokyo.cljも含め、この手のイベントは結構参加者募集のチャネルがTwitter偏重になってるきらいがあるので、今回のClojure夜会が何かしらの方法で非Twitterユーザにもリーチしたんだとすればすごいなぁと思う。


中身の方にも一応触れておくと、イベント中には、あいかわらず絶好調の川島さんのSIer芸が炸裂したり、

Clojureアイドルが誕生したり、

次回のClojure夜会にはRich Hickeyが来るかも!?という情報があったりと終始盛り上がりの絶えない楽しいイベントでした。これが一過性のブームに終わらないためには次回以降こそが大事でしょう。この勢いに乗って、(ニャンパス登尾さんも言っていたように)Clojureキャズムを超えることを期待したい。

最後に、こんな素晴らしいイベントを企画して下さった主催者の方々、ありがとうございました。

*1:Cognitectの中の人で、"Practical Clojure"やO'Reillyの"ClojureScript: Up and Running"の共著者の一人

*2:非公式には50人とか70人とか聞いたけど

CodeIQ プログラミング言語☆総選挙の問題をClojureで

先月、CodeIQでプログラミング言語総選挙なるものをやってました。

CodeIQ プログラミング言語★総選挙|CodeIQ

内容はおおむね、各人が好きな言語へ投票して言語ごとの得票数を競うものでした。まず予備選挙をやって、上位の8言語だけが本選挙に勝ち上がれるというやや手の込んだ形式です。
ユニークなのは、自分のお気に入りの言語へ投票するには与えられた問題をその言語で解かなければいけないという点。選択可能な言語はIdeoneで実行可能であればOKということみたいだったので、Clojureで問題を解いて一票入れてきました。

先週になってようやく問題の解説が出たので、ここで自分の解答を公開しておきます。

問題

細かい制約条件は異なっていたものの、予備選挙・本選挙とも基本的には同じ問題が出題されました。

大雑把には、矩形領域の中に格子状に並ぶすべてのマスに対して、上下左右に隣接するマス同士で重複のないペアを作ることができるかどうかを問う問題でした。ただし、マスには他とペアになれない"穴"が存在することがある設定になっています。

たとえば、以下のような入力の場合("O"が他のマスとペアになれるマス、"X"がペアになれない"穴"を表す)、

OOO
OOO
OOX

次のようなペアの作り方があるので、"yes"と回答します(同じ数字のマスが1つのペアを表すとする)。

112
332
44X

一方で、

OOO
OOX

OOO
OOO
OXO

の場合は、どう頑張ってもペアが作れないので"no"と回答します。

解答

解法には単純に深さ優先探索を使いました。問題自体はペアの作り方を求めることを要求してませんが、解答はペアの作り方がある場合には最初に求められたペアの作り方を返すように作ってあります。

(ns codeiq-pairs.core
  (:require [clojure.java.io :as io]
            [clojure.string :as str]))

(defn read-table [rdr]
  (mapv vec (line-seq rdr)))

(defn assign [table index val]
  (when (= (get-in table index) \O)
    (assoc-in table index val)))

;; for debug
(defn print-table [table]
  (println (str/join \newline (map str/join table))))

(defn next-unassigned-cell [table [i j]]
  (letfn [(cells-from [i j]
            (lazy-seq
              (cond (>= i (count table)) nil
                    (>= j (count (first table))) (cells-from (inc i) 0)
                    :else (cons [[i j] (get-in table [i j])]
                          (cells-from i (inc j))))))]
    (ffirst (filter #(= (second %) \O) (cells-from i j)))))

(defn solve
  ([table] (solve table [0 0] 0))
  ([table index n]
     (and table
          (if-let [index' (next-unassigned-cell table index)]
            (let [table' (assign table index' n)
                  next-step #(solve (assign table' (% index') n) index' (inc n))]
              (or (next-step (fn [[i j]] [i (inc j)]))
                  (next-step (fn [[i j]] [(inc i) j]))))
            table))))

(defn -main []
  (if (solve (read-table (io/reader *in*)))
    (println 'yes)
    (println 'no)))

Clojureみたいに、イミュータブル(かつ永続的)なベクタを持っていると、こういうバックトラックもシンプル(かつ比較的効率的)に書けていいですねー。

雑感

結局、選挙自体の結果としては、Clojureは得票数3で11位、残念ながら予備選挙を通過することができませんでした。まぁ、こういうのは母数が多くないとなかなか勝ち上がるのは難しいですねー。Haskellはそれでも本選挙まで残って、C#と並んで5位だったみたいですけどね。何なんでしょうね。あの人気は。

ところで、どう書く?orgが流行ってたくらいの時期には、こうやって「俺はこの問題をこう解いた」みたいなのをそれぞれが自分のブログに掲載したりトラックバック飛ばし合ったりとかよくやってたなぁ、なんていうのを思い出しました。僕はああいう牧歌的な雰囲気が結構好きでした。
最近では、CodeIQみたいなサービスも増えてきて、単に自己顕示欲を満たすだけじゃなくて、自分の解答した成果が就職にもつながるかもしれなかったりして、まったく便利な世の中になったもんです。でもその分、「この解答は公開しちゃダメだ」とか気にしなきゃいけなくなって、昔よりもギスギスした雰囲気になったようにも感じます。まぁ、「昔はよかった」なんて懐古に走るようになったのは、単純に今の自分が昔より歳を取ったってだけなのかもしれないけど。