Emacsへの知の集約
Emacsでアプリを作ることについての自分の考えのまとめ。
Emacsの2つの側面
Emacsの可能性を議論する場合に、エディタ・IDEとしてのEmacsと、アプリケーション実行環境としてのEmacsの2つの側面を分けた方が良いかなと思っています。両者がごっちゃになるので、「Emacsは環境だ」→「エディタでメールとか変態だ」→「むきー!!!」という不毛な議論になるのだと思っています。
単純なエディタやIDEとしてのEmacsについては、十分議論が出ていると思いますので、ここでは議論しません。個人的には、趣味なら自由、仕事でやるなら望まれた生産性を満たす好きなツールを使えばいいと思っています。
今回の議論は、アプリケーション実行環境としてのEmacsです。こちらは、 Eclipse RCP や FLEX、JVM系各種スクリプトや、各種Webフレームワーク、GAE や HTML5 といった言葉と比べるようなものだと思っています。つまり、「GWTとFLEX/S2のどちらでメーラーアプリを作る方が良いか?」という感じで、「GWT、FLEX/Java、Eclipse RCP、EmacsのどれでIMAP4メーラーアプリを作った方が、工数やメンテナンス性の面で良いか?」という質問になるかと思います。
ほら!全然違和感ない!むしろ、Emacsの方が楽そうに見えますね!不思議!!!
Emacs上のアプリケーション使ったり、開発することはそんなにいいのか?
結論から書くと、まずEmacsは単体のアプリを使ったり書くにはあまり向いていません。単体のアプリを書くのであれば他の実行環境を使えばいいと思います。しかしながら、知的活動を支援するたくさんのアプリがそろってますので、もう全部Emacs上に載せてしまって、一連のワークフローを強化するようにカスタマイズしたり、必要なアプリを書くには悪くない選択だと思います。
アプリケーション実行環境としてのEmacs
Emacsの動作環境はかなり広いです。大抵のOSで動作します。GUI付き、端末画面、画面無しのバッチ実行など、動作方法も選べます。開発最先端は多少不安定なことがありますが、リリース版はかなり安定していることが期待できます。処理系としてはかなり枯れている方ですが、今でも精力的に開発が進んでいて進化し続けており、今後も長期的なサポートが期待できます。
実行速度は普通のLL的です。シンプルなスタックマシンのVMで動くので、バイトコンパイルすると多少最適化がかかって速くなります。
言語の機能ですが、伝統的な LISP-2 で、ダイナミックスコープが標準です。レキシカルスコープはもう少ししたら標準で使えるようになりますが、今でもマクロで擬似的に利用可能です。モジュールや名前空間がないので、お行儀良くするには C のように関数や変数に接頭辞をつける必要があります。末尾再帰最適化、リーダーマクロが無く、組み込みの構造体、OOP支援、パターンマッチ、正規表現リテラルはありません。CLの機能をエミュレートするclマクロ集が標準で付いています。
実行環境やライブラリは、もともとエディタなので文字列処理系が強いです。日本語も含めて各種文字コードをある程度まともに扱えます。正規表現、ファイルIO、外部プロセス、ネットワーク、日付・時刻、その他基本的な処理は大体そろっています。また、HTTP通信、非同期、OOP、XML、JSON関係などの最近流行っているような機能のライブラリもそろっていますし、EmacsWiki等を探せばいろいろみつかります。スレッドのサポートは無いので、Node.jsのようにシングルスレッドの非同期プログラミングを駆使します。PerlのCPANやRubyのGEMのようなパッケージ管理がいくつかあるのですが、まだ決定的なものが無いような状況です。
GUIについてですが、一般的なメニュー、マウスでのコンテキストメニュー、ツールバーがあります。画面に画像も表示できますが、現バージョンではEmacs内でサイズの調整が出来ないため、ImageMagickなどを使って自前でサイズを調整する必要があります。ボタン、入力フィールドなどの一般的なGUI部品はありますが、使いこなすにはちょっと癖があります。さらに、ウインドウという表示領域を区切る仕組みがあり、基本的にどのようにウインドウを区切るか、またどのバッファを表示するかはユーザー側が決めるような仕組みになっています。このように、GUIまわりはテキストエディタ由来なので癖のある挙動になります。
Emacsならではの非常に強力な機能としては、日本語migemo検索(migemoは別途インストール必要)とAnythingインタフェースが挙げられます。これらを使うためだけでも実行環境としてEmacsを選ぶ価値があります。
開発環境としては一通りそろった Emacs があります。REPL、アウトライン、補完、ドキュメント参照、定義ジャンプ、デバッガなど、必要なものは大体全部そろってます。
こうやってみると良いのか悪いのか微妙ですが、スペックとしては悪くない方だと思います。結構みんなが困っているのは名前空間、レキシカルスコープが無いことでしょうか。個人的にはDBとの接続が欲しいです。あと、ESGI/Elackとか出来た日には、Emacs LispでWeb開発が始まると思います。
それぞれの環境には一長一短があるので、Emacs Lispを使うかどうかは、やりたいことと困ることのトレードオフで決めればいいかなと思います。
ワークフローとEmacs
アプリにはそれぞれ想定するユースケース(利用状況)があります。一般的にはワークフロー(業務手順)の一部がユースケースに当てはまり(もしくは当てはめて)、そこでアプリを利用することになります。ワークフローによっては、いくつかのアプリを組み合わせる必要が出てきます。このとき、これらのワークフローを支援するアプリ達がうまく繋がれば作業効率が上がります。
例えば、Backlog、RedmineなどのいわゆるBTSでは、単なる課題を管理するWebアプリだけでなく、通知用のメール、ドキュメントとしてのWiki、SubversionやGitなどのVCSを、課題情報を中心にうまく接続することで、よくある開発チームのワークフローをサポートして効率を上げることが出来るようになっています。
同様に、Emacsでも各種アプリを自分のワークフローに合うように接続することで、自らの生産性を向上させることが出来ます。その鍵となるのが情報が集まるツール、つまりメモアプリやコミュニケーションアプリ(メール、Skype、IRC、Twitterなど)です。Emacsにはメモやコミュニケーションなど、知的活動を支援する数多くのアプリがあります。これらのアプリを自分のワークフローに合うように組み合わせることで、効率的な作業環境を実現することができます。
ということで、Emacs上で知的活動が完結するように、必要なアプリを開発することは十分な動機になると思います。
引きこもり、変態、あるいはユートピア
以上のようなことを書くと、「ひきこもり」「変態」と言われるわけです。しかしながら、個人の情報管理・コミュニケーションを単体でサポートするツールはたくさんあるのですが、それらを自分のワークフローに合うように組み合わせてカスタマイズしていける環境はあまりありません。
自分の乗り換え用として、また他人に勧めるために、Emacsに限らず今も探しているところです。
大昔は、システム手帳を使っていました。すごく良かったです。でも、検索できないことで困ってました。また、年が変わる時期の物理的な制約も気になってました。その後は何台かPDAを使っていました。これらもすごく良かったのですが、PC上のメールが多くなり始めてから、PCとPDAでデータが分かれてしまうことが気になっていました。そのあとしばらくいろいろ試行錯誤の結果、Emacsに全部まとめる方法に落ち着きました。howm最高です。
Outlookはメールだけではなく、アドレス帳(名刺管理)、スケジュール、タスク、メモ帳などの一通りの機能がそろっています。GNOME だと Evolution があります。これらは普通の人が普通に使うには良いと思います。しかしながら、個人的には常用するには至りませんでした。機能やスペックは良いのですが、ヘビーに使うと細かいところで不便なところがたくさん出てきたり(特にパフォーマンスやUIに不満がある)、データの再利用などカスタマイズが難しいところが難点です。
Web上のサービスにも情報を管理するツールはたくさんあります。最近は、技術向上によりWeb上で出来ることが増えてきたり、スマートフォンからも使えるようになってきたことから、今後はローカルではなくクラウドで使うことが普通になりそうです。Evernoteを中心にして、周辺にエコシステムが形成されているのが良いですね。ただ、現在の自分の仕事のスタイルとしてオフライン(客先や移動中など)で使えないと困ることから、まだ様子見で止まっています。
いろいろな環境がありますが、すべてを一つの環境に統合して便利にしようという方向はそれぞれ一緒だと思います。なので、何かを我慢して既存のアプリを使うか、Emacsで自分でカスタマイズしながら頑張るかの違いでしかないと思います。
いろいろ脱線しましたが、要するにEmacsが最高と言いたいんです。
concurrent.el リリース
今まで何の説明も無しに自分のアプリで使ってきた謎ライブラリ concurrent.el ですが、一区切りが付いた気がしましたのでリリースしたいと思います。
この記事では concurrent.el の基盤である deferred.el について簡単に紹介して、 concurrent.el の機能と適用例を紹介します。
あらすじ
- deferred.el復習
- concurrent.el紹介
- 機能一覧、コード例
- cacoo.elでの設計解説
deferred.el 紹介
deferred.el の詳しい使い方やAPIなどはREADMEの文書がまとまっていますので、手っ取り早く使いたい人はそちらを参照してみてください。 おそらく、他の言語でDeferredに慣れていればすぐに使えるのではないかと思います。
- deferred.el リリース - 技術日記@kiwanami (リリース記事:慣性スクロールがメインかも)
- deferred.el の README (簡単なコード例や関数リファレンスなど)
- deferred.el のできるまで:調査、設計と実装 - 技術日記@kiwanami(一般的な「Deferred」についての情報いろいろ)
Deferred / 非同期タスクをつなげていく
deferred.el は非同期のタスクをつなげていくプログラムをつくっていきます。具体的には、ある「コールバック1」を実行した後で「コールバック2」を実行するというような、コールバックの連鎖を構築します。書いているプログラムはコールバックのつなげ方を指示するようなプログラムになります。
つまりコールバックの書き方を少し変えただけなのですが、ソースコードが格段に分かりやすくなり、また非同期をつなげるときの柔軟性もはるかに向上します。また、エラー時の処理も分かりやすくもれなく書くことが出来るため、プログラムの信頼性も上がります。
JavaScript上の実装である JSDeferred を参考に実装していますので、そちらになれている人はすぐに使えると思います。また、JavaScriptからすると5年以上後発であるため、基本APIは枯れていてかなり固まっています。おそらく、今後も機能追加はあったとしても非互換な修正はほとんど無いだろうと思っています。
基本機能一覧
簡単に機能を紹介します。
非同期タスクの開始 | |
---|---|
すぐに開始 | deferred:next |
一定時間待って開始 | deferred:wait |
複数のタスクを並行に開始 | deferred:parallel, earlier |
リストや数を受け取ってループ開始 | deferred:loop |
同期的に開始 | deferred:succeed |
外部プロセスを開始 | deferred:process など |
HTTP接続を開始 | deferred:url-retrieve など |
タスクをつなぐ | |
---|---|
前のタスクにつなぐ | deferred:nextc |
エラー時のタスクをつなぐ | deferred:error |
動的につなぐ | タスクの返値で次に実行したいDeferredオブジェクトを返す |
正常・エラーにかかわらずタスクを割り込ませる | deferred:watch |
非同期でのtry-catch-finally | deferred:try |
待ち合わせ | |
---|---|
並行タスクが全部終了するまで待つ | parallel |
早く終了した一つのタスクだけを待つ | earlier |
活用例
自分ツールでは結構使っています。今後も必須になると思っています。
- deferred.elのみ
- concurrent.elと一緒に
deferred.elのリリース時にも少し紹介いたしましたが、自分以外の方も興味を持っていただいたみたいでうれしいです。
非同期で欲しくなるもの
非常に便利な Deferred なのですが、 慣れてくると処理を切ったりつなげたりというかなり低レベルな処理が続くことに気がつきます。また、多くの場面で似たような Deferred 処理を書くことも多くなってきました。
どのような場合があるのかまとめてみました。
- 実行順序の制御
- 複数のタスクを非同期に実行したいが、順序や依存関係で制御したい
- →データフロー
- スレッド
- UIにアニメーションを手軽に入れたい
- バックグラウンドで処理を小刻みに行いたい
- 非同期プロセス間の通信
おそらく、これはEmacsだけの問題ではなくて、非同期をたくさん使う場合にはみんな同じ悩みを持っているのではないかと思います。
そこで deferred.el の上に、上記のような良くある機能を構築してみました。
concurrent.el
基本的には、上で述べたような要求を満たすような機能を設計・実装しました。その際、これまでの開発の経験や、マルチスレッド、並行プログラミングの考え方などを参考にしました。
ただ、並行プログラミングやGUIの専門家ではないので、良い方法をがあれば是非知りたいと思っていますし、 concurrent.el の実装自体についても議論があれば教えて欲しいと思っています。是非みなさま、よろしくお願いします。
開発は deferred.el と同じリポジトリに入っています。
機能一覧
現在の機能一覧です。コードサンプルについてはすぐ後で例示します。(Wikipedia便利ですね。)
- thread
- 適当な単位で処理を分割して実行するスレッド
- generator
- スレッド実装をちょっと改変したもの。おまけ。
- 無限データや、fiberみたいな感じ。
- semaphore
- いわゆるセマフォ
- 同時実行数を1にして、ロックやメッセージキューとして使ってもいいかも
- dataflow
- 値がバインドされるまで処理を止めておく仕組み
- データフローパターン、Futureパターンのようなもの
- ガウディ本のデータフロー変数(並行論理変数)をイメージ
- 変数へのアクセスを下の signal を使って通知できる。特に未バインド変数アクセスのイベントは、 Ruby の method_missing っぽくて強力。
- signal/channel
ここに挙げた機能は、いくつかツールを作って検証しているので自分の中ではある程度固まっています。ただ、自分一人しか使っていませんので、今後修正や機能追加があれば、互換性を維持しながら積極的に行っていきたいと思っています。
また、今後以下の項目についても必要であれば実装しようかなと思っています。
- モニタ
- 遅延実行
- プロセス間通信、comintのサポート
- 差分リスト?
コード例
- concurrent-sample.el (以下で紹介するものと同等の内容)
Theadの例:lexical-letを評価するとその場でアニメーションします。引数の時間は、bodyの処理の間隔です。
(lexical-let ((count 0) (anm "-/|\\-") (end 50) (pos (point))) (cc:thread 60 (message "Animation started.") (while (> end (incf count)) (save-excursion (when (< 1 count) (goto-char pos) (delete-char 1)) (insert (char-to-string (aref anm (% count (length anm))))))) (save-excursion (goto-char pos) (delete-char 1)) (message "Animation finished.")))
whileを使うことでスレッドをループさせることが出来ます。whileの中身は一気に実行されます。
無限ループや重い処理でEmacsが固まらないように注意してください。もし無限ループに突入してしまったり、固まってしまったら deferred:clear-queue コマンドで回復できる可能性があります。
Generatorの例:fib-genにジェネレーターを作ります。ジェネレーター生成body内のyield関数で値を返します。非同期なのでしょうがないのですが、コールバックで値を受け取るところがいまいちかも知れません。
(setq fib-list nil) (setq fib-gen (lexical-let ((a1 0) (a2 1)) (cc:generator (lambda (x) (push x fib-list)) ; コールバックで結果受け取り (yield a1) (yield a2) (while t (let ((next (+ a1 a2))) (setq a1 a2 a2 next) (yield next)))))) (funcall fib-gen) ; 何度か呼んでみる (funcall fib-gen) (funcall fib-gen) (funcall fib-gen) (funcall fib-gen) fib-list ; => (3 2 1 1 0)
Semaphoreの例:cc:semaphore-acquire 関数が deferred を返すので、それに続けて実行させたいタスクをつなげていきます。時系列で挙動が変わっていくのでコード中に簡単な説明を書いてみました。
;; permit=1のセマフォ作成 (setq smp (cc:semaphore-create 1)) ;; 続けて3つ実行しようとする (deferred:nextc (cc:semaphore-acquire smp) (lambda(x) (message "go1"))) (deferred:nextc (cc:semaphore-acquire smp) (lambda(x) (message "go2"))) (deferred:nextc (cc:semaphore-acquire smp) (lambda(x) (message "go3"))) ;; => 1つ目だけ実行されて go1 が表示される (cc:semaphore-release smp) ; permitを返す ;; => 2つ目が実行されて go2 が表示される (cc:semaphore-waiting-deferreds smp) ; go3 を表示するdeferred (cc:semaphore-release-all smp) ; => permitを初期化して go3 を表示するdeferredを返す (cc:semaphore-waiting-deferreds smp) ; => nil
Dataflowの例: cc:dataflow-environment 関数で変数を格納する「環境」を作ります。 cc:dataflow-get は値の取得とそれに続くタスクをつなげる deferred を返します。 cc:dataflow-set で値をバインドします。例ではキーに文字列を使っていますが、キーには任意のオブジェクトを指定できます。
(setq dfenv (cc:dataflow-environment)) ;; ○基本の使い方 ;; ↓同期的に値を取得。ブロックしない。 (cc:dataflow-get-sync dfenv "abc") ; => nil まだ値が無い。 (deferred:$ ; abc という値を取ってきて表示する処理 (cc:dataflow-get dfenv "abc") (deferred:nextc it (lambda (x) (message "Got abc : %s" x)))) ;; => 値がないので処理はブロックしたまま (cc:dataflow-set dfenv "abc" 256) ; 値をセット ;; => ここで先ほどブロックしていた処理が再開し、 "Got abc : 256" が表示される (cc:dataflow-get-sync dfenv "abc") ; => 256 (cc:dataflow-clear dfenv "abc") ; 値を未バインドに戻す (cc:dataflow-get-sync dfenv "abc") ; => nil ;; ○リストをキーにする (deferred:$ (cc:dataflow-get dfenv '("http://example.com/a.jpg" 300)) (deferred:nextc it (lambda (x) (message "a.jpg:300 OK %s" x)))) (cc:dataflow-set dfenv '("http://example.com/a.jpg" 300) 'jpeg) ;; => a.jpg:300 OK jpeg ;; ○2つの値を待ち受ける (deferred:$ ; abc, def の2つの値を使う (deferred:parallel (cc:dataflow-get dfenv "abc") (cc:dataflow-get dfenv "def")) (deferred:nextc it (lambda (values) (apply 'message "Got values : %s, %s" values) (apply '+ values))) (deferred:nextc it (lambda (x) (insert (format ">> %s" x))))) ;; => もちろんブロックする (cc:dataflow-get-waiting-keys dfenv) ; => ("def" "abc") (cc:dataflow-get-avalable-pairs dfenv) ; => ((("http://example.com/a.jpg" 300) . jpeg)) (cc:dataflow-set dfenv "abc" 128) ; ここではまだブロックしたまま (cc:dataflow-set dfenv "def" 256) ; ここでやっと動く ;; => Got values : 128, 256
Signalの例: cc:signal-channel でシグナルを流すチャンネルを作成します。その後、signalに応答する処理を接続していきます。
;; シグナルのチャンネルを作成 (setq channel (cc:signal-channel)) (cc:signal-connect ; foo というシグナルを拾う channel 'foo (lambda (event) (message "Signal : %S" event))) (cc:signal-connect channel t ; t にするとすべてのシグナルを拾う (lambda (event) (destructuring-bind (event-name (args)) event (message "Listener : %S / %S" event-name args)))) (deferred:$ ; deferred で非同期タスクを接続できる (cc:signal-connect channel 'foo) (deferred:nextc it (lambda (x) (message "Deferred Signal : %S" x)))) (cc:signal-send channel 'foo "hello signal!") ;; => ;; Listener : foo / "hello signal!" ;; Signal : (foo ("hello signal!")) ;; Deferred Signal : (foo ("hello signal!")) (cc:signal-send channel 'some "some signal!") ;; => ;; Listener : some / "some signal!"
dataflowの内部には、変数へのアクセスやバインドのシグナルを発信するchannelがあります。これを使って、未バインドの変数に値を作成してセットするようなことが出来ます。
signalやdataflowは、カスケード接続して親子関係を構築できます。例えば、親dataflowにデフォルト値(フォールバックの値)を入れておくとか、channelで親子関係を構築してローカルなイベントとグローバルなイベントを分けて効率的にイベントを管理するなどが出来ます。
活用例紹介
前述の非同期で欲しくなるもののひっくり返しになりますが、自分のツール(anything-books.el, cacoo.el, 3D迷路)で使っているところを簡単に紹介します。後で、cacoo.el についてはもう少し詳しく解説したいと思います。
- 通信・プロセス数制限
- semaphoreで実行数制御
- アニメーション
- threadで動作中のアニメーションを表示
- 処理本体からの signal イベントを拾って、スレッドの開始や停止
- ウインドウ間の通信
- signal/channel で表示要求や処理イベントを通知
- イベント受け取り先のウインドウやバッファがいなくてもかまわない
- プロセス間の通信
- 通信プロトコルを signal/channel でラップ
- 透過的なメッセージパッシング通信を実現
- キャッシュ、依存関係
- dataflow でキャッシュ管理
- キャッシュデータ間の複雑な依存関係やイベントを管理
- 依存関係により勝手に処理がブロックし、準備が出来たら再開する
- キャッシュデータ要求の signal を拾って、キャッシュミスならデータの取得や加工を開始
cacoo.elのアーキテクチャ
cacoo.el は本格的に concurrent.el を使ってみたツールです。いろいろなところに concurrent.el の機能を使っていますが、そのうちの重要ないくつかについて解説してみます。
cacoo.el 自体については以前の記事を参照してください。
全体
プログラムは大きく分けて2つに分かれます。一つはバッファの中のマークアップを画像で置き換える処理。もう一つは Cacoo API に接続して、Cacooの絵をAnythingで選択して貼り付ける処理です。
前者のマークアップの画像を置き換える処理では、「wgetで画像取得」「convertでサイズ変更」「画像をバッファに表示」という一連のタスクがあります。また、それぞれのタスクの成果物はキャッシュとしてローカルに保存されます。これらをうまく管理することが一つの目標です。
後者のCacooAPIに接続する部分では、バックグラウンドのネットワーク処理は deferred.el で行いますが、Anythingの起動を遅延させたり、プレビュー画像を表示させたりするところで concurrent.el を使っています。
semaphoreによるプロセス数制御
通信、画像取得、ImageMagickによるリサイズ処理は、外部プロセスを起動しています。これらは非同期に実行されますが、何も考えずに非同期で次々に実行させると、大量の外部プロセスが同時に起動してしまいます。数個程度であればそれほど問題ないのですが、何十個も画像がある場合は、同時に大量のHTTPアクセスが発生してサーバーに拒否されたり、あるいは大量のプロセスが起動してメモリ不足になってしまうなどの、ユーザーにとって好ましくない状況になります。
通常、このような同時実行するタスクの量を制限したい場合にはセマフォを使います。 cacoo.el では、設定変数の cacoo:process-num で制限するプロセス数を指定し、実際には cacoo:process-semaphore にセットされるセマフォオブジェクトで同時実行タスクを管理します。デフォルトは4になっています。
cacoo:process-semaphore は各地の非同期タスクの中で横断的に出てくるのですが、全体を通して起動されるプロセス数が上限を超えないように調整されます。
もし、上限を超えるプロセスの実行が要求された場合、セマフォがその処理をブロックします。そして先に実行されている処理が終了して余裕が出てきた段階で、待たせておいたタスクを実行します。
emacs-w3m にも同様の処理キューのような仕組みがありますが、特別に非同期を扱うためにかなり複雑なコードになっています。非同期タスク用セマフォは一般的にニーズが高いと機能ではないかと思っています。
thread によるアニメーションと signal による処理状況の通知
時間のかかるタスクを実行しているとき、ユーザーに何らかの進行状況の通知をするべきです。通常のGUIアプリであれば、プログレスバーを表示したりアニメーションを表示するものが多いです。
cacoo.el では、Anythingでのプレビュー画像表示のところで、くるくる回るアニメーション表示と、進行状況の通知を行っています。
concurrent.el の thread を使うことでアニメーションの更新・停止処理をシンプルに書くことが出来ました。thread が無ければ自力でタイマーを駆使したりフラグで止めたりしなければならず、やりたいことの割には複雑なコードになります。
また、処理状況の通知については、メインの処理である「取得→リサイズ→表示」の非同期タスクが処理状況を signal でブロードキャストし、それをGUI周りのコンポーネントが適当にイベントを拾ってきて更新するというような構成にしました。実際にはまだ整理し切れてないところもあるのですが、メインの非同期処理とGUIのコードがある程度分離できたように思います。
Dataflowによる画像キャッシュ管理
画像のマークアップを表示するためには、画像取得とリサイズを行う必要があります。毎回画像を取りに行くのは無駄ですので、取ってきたオリジナルの画像とリサイズした画像はローカルにキャッシュしておきます。この処理の流れとキャッシュデータの依存関係を表すと下の図のようになります。
矢印が依存方向ですので、左側がデータの上流です。
最初、データの流れに従って左側から順に非同期タスクを処理するようなプログラムを書いていました。図に書くと、下図のようです。
キャッシュについては、キャッシュがあるかどうかを調べて、あればタスクをスキップします。
この方法には問題がいくつかあることが分かりました。まずマークアップごとに独立したタスクになるため、URLが同じ画像があっても、無駄に画像を取りに行ってしまったり、データや処理が競合して正しく画像が表示できなくなります。
また、「画像を取りに行ってリサイズして表示するまで」が一つのアトミックな単位になるため、処理をバラバラにして非同期タスクのスループットを上げるようなことが出来ません。
そこで、データフロー変数を使って依存関係をそのままプログラムに落とし、依存関係の間は非同期メッセージングで接続することにしました。絵に描くと下図のようです。
これにより、アトミックな非同期タスクが依存関係の矢印1つ分に分割され、効率よくタスクを実行することが出来るようになりました。さらに、データフロー変数で全体のデータを共有するようになったため、マークアップ間で同じURLがあった場合は、同一の画像データを使うようになりました。これで、競合や無駄な処理が起きなくなりました。
また、プログラム自体も短くなり、さらに各タスクが粗結合になったため、さらなる拡張もやりやすくなりました。以前の実装ではプラグインはかなり特別なコードを書いて、しかもキャッシュがうまく出来ず、効率が悪かったのですが、今回の実装ではデータフロー変数の枠組みの中で効率よく処理できるようになりました。
一方で、「画像を取りに行ってリサイズして表示する」全体のタスクがバラバラになったため、コードから全体のタスク流れを読み取ることが難しくなりました。このような依存関係をつなげていって全体を構築するアーキテクチャでは、全体を俯瞰するドキュメントが重要になります。
Dataflowによる画像リスト管理
CacooのマイナーモードをONにすると、バックグラウンドで Cacoo API からシートの一覧を取得します。もし、一覧の取得途中でAnythingを起動した場合、ユーザーにしばらく待ってもらって一覧取得後にAnythingを起動するようにします。
今回の実装ではデータフロー変数を使って、シート一覧の取得とAnythingの起動の待ち合わせを自動的に行えるようにしました。Anythingはこの変数を参照するコードを書くだけで、データがまだバインド(取得完了)されていなければ自動的にブロックするようになります。絵に描くと下図のようです。
非同期の待ち合わせはフラグなどを多用して分かりにくくなりがちなのですが、このような抽象的な機能を使うことで、誰がデータの生産者で誰が消費者なのかをコードで示すことが出来るようになります。
ただ、何度もAnythingコマンドが発行されると(ありがちなケース)、バインド待ちのタスクが溜まっていってしまうので、やっぱりフラグを使って一つだけになるように制限してしまいました。多分、差分リストのようなテクニックを使うことで、もうちょっとうまく出来るような気がするのですが、あまり高度になりすぎても意味不明になりそうだったので、この実装はこれで良かったと思っています。
今後の予定
なんとか英語でもAPIドキュメントをそろえて、 deferred.el, concurrent.el を宣伝してみようと思います。
deferred.el だけだとプリミティブ過ぎたり足りない部品があったり、良さがうまく伝わらないかなと思っていたので、ちょっと躊躇していました。あと、Node.jsでシングルスレッドの非同期プログラミング環境が広まっているので、そういうものと絡めて「シングルスレッドのEmacsでも十分カッコイイよ!」と宣伝できると良いのかなと思っています。標準で入るといいですね。
後は、並列系がやっとそろったのでやっと skype.el の非同期化と機能改良に取りかかれると思います。
その前に、calfw.el のリリースとか、e2wm.elの改善とかリリースとか、いろいろ山盛りかも知れません。。。
関西Emacsに行ってきた
ちょっと遠出して、関西Emacsに行ってきました。
福岡Emacsの企画の準備もかねて、どんな人たちがどんなことを期待して集まってくるのかをちょっと調べようというのと、最近のEmacsについて思うことについて話して議論してみたいなと思って行きました。
午前中、生でるびきちさんにお会いすることが出来ました。電車の中でAnythingと非同期について議論させてもらいましたが、ちょっとしかお話しできなかったのが残念でした。
会場は30人ぐらいぎっしりで、若い人から年季の入った熟練ユーザー、さらに女子の方もいて、たかがテキストエディタをネタにこんなに集まれるのがすごいですね。場所を提供していただいた株式会社アックスさん( id:syohex さん)ありがとうございました。懇親会のお店もとてもよかったです。
前回に引き続きポジションペーパーが多くて、配布や紙を見つけるのが大変だというところが改善できればいいなと思いました。次回から運営委員が結成されるようなので、id:peccuさんの負担が減って盛り上がっていくと良いなと思いました。
当日は体調不良であんまり元気が無くて、みんなとお話しする気力が尽きてしまいました。すいません。
みなさんの発表について
設定ファイル改善 (id:IMAKADOくん)
分割して管理しやすくすると言うことと、読み込み途中でこけないようにするということでした。sysv-init 的な数字で読み込み順序の管理と来れば、行く末はイベントドリブンな upstart 方式になるのかなとか思いました。
自分の所は適当にしか分割してないのですが、 global-set-key 達はバラバラだと分からなくなったので、一つにまとめています。
VirtualBox制御、org-modeなど (id:r_takaishiさん)
常にEmacsが居る人にとっては、Anythingの素早さ強力さが便利ということにつきるのだと思います。
org-modeは最近使い始めたばかりだったので、よく使っている人の操作を見れたのがよかったです。後でcalfw-orgのご意見も頂きました。
Sekka (@kiyokaさん)
自分も yc がすごい好きだったので、sekkaのモードレスな感じはすごい素敵です。アルファベット混じりだとどうしても文節区切りが短くなってしまうので、ATOKを yc みたいに使えないかなとか考えていたときがありました。ちょっと余裕があったら実装を参考にしてみたいです。
やっぱり俺Lisp実装や通信周りが気になりました。
リリースについて (@naota344さん)
たくさんものを作っては放置気味になっている自分としては、大変耳が痛いお話しでした。
継続したメンテナンスも大変なのですが、リリース作業はとても大変です。リリースのための準備というのはかなりたくさんあって、やってみて初めて分かることばかりで、モチベーションが下がってしまうと言うのはその通りです。でも、リリースして反応があるとうれしいので次も頑張ろうという気になるわけです。
パッケージメンテナーの方がnaotaさん以外にもおられて、calfwのパッケージ化のお話しがいただけたり、パッケージについてのお話しを聞けたのがよかったです。
自分の発表について
かなりgdgdですみません。予定では、始めに3D迷路をみんなで遊んでから、「Emacsはやれば出来る子だから!!!」という流れで、 Beautiful な Emacs というお話しする予定でした。言い訳にはなりますが、自機の無線が不調だったり、OSが固まったりと、いろいろ崩れてしまいました。手元のメモによると以下のようなことを話したかったようです。(迷路のデモについては別エントリで続けます。)
- 発表の目的
- 広い視野を見よう
- 一通り完結する大きな物を作ろう
- Emacsでハッピーライフを
- 自己紹介
- 開発環境としてのEmacs
- 言語処理系としてのEmacs
- 言語間の対立は多い
- HSP vs Ruby, Java vs PHP vs Ruby, Common Lisp vs Scheme vs Emacs Lisp など
- でも世の中を見てみると、disられる側の言語でつくられた「役に立ってる」プロダクトは多い
- 設計・実装・コードはイマイチでも、それでみんなの問題が解決する
- でも設計・実装のキレイさも重要。負の遺産。
- 作りたいものに興味があるか、言語自体に興味があるかどうかの違い?
- 言語間の対立は多い
- Emacsに偏らず、いろいろ試していいものを吸収していこう!
- アプリケーションを作ろう
- Emacsではちょっとした拡張がやりやすい
- GreaseMonkey みたいな感じ。ちょっとした改善で生活が豊かになる。
- そうではなくて、なにかまとまった大きな物を作ってみよう
- そこから得られる物は大きいよ!
- UIは難しい
- 抽象化について
- Emacsで大きな物を作るのは楽しいし、良い経験になるよ!
- これからのEmacs
- Emacs使っていいんです!
- 新聞社・出版社と同じく撤退戦
- 長い目で見ればクラウド化するかもしれないが、数年のスパンではまだまだ現役
- 「拡張可能高機能エディタ」という本質が重要であって、プラットフォームは時代に合わせて変化していけばいい。
- ローカルの利点を生かす
- オフライン, 速度, 大容量
- クラウドと友達になる
- テキストエディタを超えて
- 単に「使える」レベル以上を目指す
- 使っていて楽しい、かっこいい
- → Apple的な感じ
- 魔法のようなエディタ
- →映画に出てくるようなカッコイイものをめざす
- 一般人にも勧められる物を
- 普通の人が普通に便利に使えるツールを
- 単に「使える」レベル以上を目指す
- これからもEmacsを盛り上げていきましょう
多分、当日言ってないことも多々あるかも知れません。
次の機会は頑張りたいと思います。
Emacsで3DなマルチプレイヤーなFPSを作ってみた
関西Emacsで発表したデモです。
まず、「FPS」は CoD MW のようなシューティングとかではなくて(それは是非やりたかったのですが)、一人称散歩(First Person Sanpo)プログラムです。FPSは釣りです。ごめんなさい。
(2011/05/06 追記: Vimにも出来てました!! 3D in Vim — KaoriYa)
動かし方:シングルプレーヤー
必要な物:
- 64bit版Emacs23.x
- 22でも動くかも知れません
- 32bitだと整数桁あふれするそうです
- banner
- deferred.el, concurrent.el
- 非同期ライブラリ
- matrix.el
- 簡易行列計算
- 3dmaze.el (本体)
各elispファイルは、auto-install.elで以下の式を評価して入れるか、ダウンロードしてload-pathの場所に置いてください。ダウンロードした場合は、なるべく高速に動作させるためにバイトコンパイルしてください。(多分2倍くらい速くなります)
;; auto-installを使う場合 (auto-install-from-url "https://github.com/kiwanami/emacs-deferred/raw/master/deferred.el") (auto-install-from-url "https://github.com/kiwanami/emacs-deferred/raw/master/concurrent.el") (auto-install-from-url "https://github.com/kiwanami/emacs-3d-demo/raw/master/matrix.el") (auto-install-from-url "https://github.com/kiwanami/emacs-3d-demo/raw/master/3dmaze.el")
- ダウンロード
準備出来たら、以下のように操作します。
- M-x load-library RET / 3dmaze RET
- Scratchバッファで (require '3dmaze) を評価しても可
- 迷路を適当なバッファに描く
- 迷路のバッファで M-x d3m:open-maze-buffer
初期位置はランダムで決まりますので、壁の方を向いていたら真っ青から始まるかも知れません。あわてず横を向いてください。
あと、行頭行末スペース強調をしている人は、止めておいてください。
キーバインド
キー | 動作 |
---|---|
←→ | 左右に回転する |
↑↓ | 前に進む、後ろに進む |
a,s | 左右に進む |
b | 後ろを向く |
m | 2Dマップ表示のトグル |
終了は kill-buffer (C-x k) してください。
迷路について
「#」や「*」は壁になります。スペースは道になります。
アルファベットを書くと、アルファベットのオブジェクトになります。迷路バッファとして適当なソースファイルとかも指定できますが、オブジェクトが多いと重くなりますので注意してください。
アルファベット以外の文字(正確にはbannerが表示できない文字)を含むとエラーになるかも知れません。
動かし方:マルチプレーヤー
シングルプレーヤー版が動けば、こちらもすぐに動くと思いますが、MacのEmacs22だとIPv6の関係でサーバーになれないかもという情報があります。
シングルプレーヤー版に加えて、サーバーは server-maze.el をインストールしてください。クライアント側は client-maze.el をインストールしてください。もちろん、両方入れて自作自演も可能です。
;; auto-installを使う場合 (auto-install-from-url "https://github.com/kiwanami/emacs-3d-demo/raw/master/server-maze.el") (auto-install-from-url "https://github.com/kiwanami/emacs-3d-demo/raw/master/client-maze.el")
- ダウンロード
サーバーの起動
カレントディレクトリの迷路ファイル(maze.txt)を読み込みますので、ホームディレクトリに迷路ファイルを置いてください。その後、Scratchバッファで以下を評価すると起動します。
(require 'server-maze) (setq ssm:server-address "192.168.1.1") ; ←自分のマシンのIPv4アドレス (ssm:server-start)
起動すると、以下のような画面が表示されます。
サーバーの画面
プレーヤー(クライアント)の起動
スクラッチバッファから以下を評価してください。
(require 'client-maze) (csm:client-start)
そうすると、以下のような画面が出てきます。
接続情報画面
「Name」は適当なアルファベット、「Server Address」はサーバーのIPアドレスやホスト名、「Server Port」は8765(デフォルトの場合)を入れてください。
OKを押して、うまくいくと迷路画面が出てきます。
操作の仕方はシングルプレーヤーと変わりません。
他人がどっちを向いているかは色で分かります。
- 赤:こっちを向いているとき
- 黄:横を向いているとき
- 緑:向こうを向いているとき
散歩ゲーなので、撃つ殴るなどのインタラクションは出来ません。仮想空間でのEmacs同士のリアルなコネクションを感じながら和んでください。
なんだこれ
目的
ちょっとEmacsの本気を調べてみようということでやりました。
調べている途中で、EmacsのVMやバイトコンパイルの内容まで興味が出てきたので、VMインストラクション表を作りました。中の人にとってもあった方が便利だと思うのですが、今後もメンテナンスが続くと良いですね。
技術いろいろ
3D表現方法
元ネタは米チャ氏の名作激走ラビリンスからです。MSXではSCREEN1でぐりぐりやるのが当時の常套手段でした。
3Dはポリゴンを計算しているわけではなくて、レイトレーシングのように画面上の各点から透視方向へのスキャンを行って、壁や床を描画しています。
画面の描画は、画面全体をスペースで埋めて、背景色を一つ一つセットすることで表現しています。裏画面としてvectorで配列を持っておき、3D描画はそのvectorに対して行って、最後にバッファに転送しています。
文字
コマンドラインからbannerを実行してフォント情報を取ってきて、それを拡大縮小することで文字を書いています。計算に余裕がなかったので向きによる傾きなどは考慮してないです。
前後関係や壁の裏に隠れたりするアルゴリズムは、Zバッファを使っています。先の裏画面vectorと一緒にZ情報も持っています。
間に合えばTwitterからアイコンを取ってきて表示しようとか思っていたのですが、時間がありませんでした。技術的には検証していますので、 straightfoward に出来ると思います。
キー操作
普通にキーバインドから描画するとEmacsが固まってしまうので、キー入力はイベントを通知するだけにして、描画側は非同期にイベントを拾って処理するようにしました。これにより、ある程度なめらかなキー入力移動が実現できました。
通信
通信はTCPストリームとS式のやりとりを concurrent.el のイベント通知でラップして、メッセージパッシングによる非同期通信で行っています。通信フォーマットがS式だとやっぱり楽ですね。
今回はこの方法がすごくうまくマッチしました。プログラム的にもシンプルになり、非同期なので各クライアントとサーバーがお互いを気にせずに自分のペースで処理を進めることが出来るようになりました。
いろいろ実験してみましたが、特定のクライアントがもたついても全体に影響が出ないところがおもしろかったです。全然規模とか比較になりませんが、MMOとかこんな風になっているのかなとか考えたりしました。
ベンチ取って最適化
最適化は定石通り、一度全体が動くようになってから行いました。大体以下のようなことをやりました。
- 幾何計算のキャッシュ(vectorへのアクセス)
- Emacs上の計算は遅いので、出来るだけ先に計算しておく
- インスタンス(vector)キャッシュ
- インスタンス生成すると遅いが、1.2倍程度しか違わない
- ほとんどの計算を固定小数点演算で行う
- 浮動小数点演算より整数演算の方が6倍程度早かったため
- faceの色文字列のキャッシュ
- ある程度種類を絞り込んで、テーブルから文字列を引いてくる。無限に色があると辛い。
後はバイトコンパイル結果を見て、ある程度VMの動きを考えながらコードを削っていきました。
まだまだ最適化・高速化の余地はあると思いますが、自分の中である程度目標が達成されたので、もうこれ以上はしないと思います。
Linux上のChromeで動くCacooエディターにスクリーンキャプチャを貼り付ける
手元のUbuntuマシンでは、Cacooの画面キャプチャ機能が使えません(SunのJava環境でも不可)。画面キャプチャは大変便利なので何とかしようと思い、Chrome拡張のChromeReplを使ってローカルの画像をCacooのFlashアプリに送り込むという回避策を考えました。
導入
手元の環境は Ubuntu 10.10 x86_64, Chrome 9.0.597.84 beta, Ruby 1.8.7 です。ChromeもRubyもapt-getで入る普通のものです。あと、画像キャプチャに ImageMagick(import, identify) も必要です。
この環境に対して ChromeRepl を入れます。以下のサイトを参考にして入れてください。これはサーバーになるChrome拡張と、それと通信するクライアントのRuby拡張から成ります。
次に、キャプチャしてCacooに転送するスクリプトを以下からダウンロードして適当なディレクトリに配置します。
- https://gist.github.com/835946 / chrome-cacoo-local-capture.rb
使い方
- ChromeをChromeRepl付きで起動します。
- Cacooにログインして新規図のエディターを起動します。
- 「画面キャプチャ」のアイコンをクリックしてキャプチャ用のアプレットを一度起動してすぐ閉じます。
- この操作は、エディターを立ち上げたときに1回だけ必要です。(将来無くなると便利ですね)
- 先ほどダウンロードしたスクリプトを実行して、適当なウインドウをクリックします。
- うまくいけばCacooのエディターにクリックしたウインドウの絵が貼り付きます。
問題なければ、上のスクリプトを適当なキーに割り当てておけば便利に使えます。
問題が起きるとすれば、何か足りないコマンドがあるとか、Chromeのポートにアクセスできないとか、そういう感じだと思います。Rubyを実行したときに出るエラーメッセージを確認してみてください。
動作解説
スクリプトは非常にシンプルなので、何をしているかはすぐに分かると思います。
- importで画面をキャプチャしてPNGで保存
- 画像のサイズをidentifyで取得
- base64で画像ファイルを文字列に変換
- ChromeReplで現在のタブのグローバル関数にアクセス
- Flashのcapture関数に画像のデータを渡す
適当に改造することで、ローカルの画像を連続してアップロードするなどに使えるのではないかと思います。
調査
キャプチャアプリとFlashがどう通信しているかを調べた(JSのコード眺めたり、jarを逆コンパイルするなど)ところ、PNGを塩無しでBASE64テキストに変換してFlashに渡しているということが分かったので、あとはそれを実現する方法を素直に持ってきました。
JSは難読化されてない限り読むといろいろ分かっておもしろいです。また、Javaについても逆コンパイルしてコードを読むのは知っておくと役に立つことが多いと思います。(もちろんEULAの関係もありますが)
ちなみに、今回の要件はたまたま素直に出来ましたが、JavaとFlashの通信の仕方によっては難しくなっていたと思います。
ローカルとWebの接続
ChromeReplによってローカルアプリとWebアプリをつなぐことが出来ます。ブラウザの操作だけでなく、表示されているコンテンツの取得や操作が可能です。ChromeReplはChrome拡張の中でEvalしているだけのような感じなので、Chrome拡張の豊富なドキュメントを読むと良いと思います。
似たものに MozRepl などもありますので、Firefoxでも似たようなことは出来ると思います。
あと、セキュリティについては気をつける必要があると思います。細かいところをつっこむといろいろあると思いますので、今回のスクリプトではCacooで図を貼りたいときだけChromeReplを有効にするような使い方にすると話が早いと思います。
無保証
この方法の公開についてCacooの中の人には了解は取ってありますが、サポートされていない使い方ですので、動作や損害については無保証です。あしからず。
cacoo.el v2.0 リリース
cacoo.el を更新しました。
表向きは Anything で Cacoo の絵が選べるようになったぐらいですが、内部的にはかなり別物になりました。非同期処理を deferred.el と concurrent.el でほぼ全面書き直ししました。その結果、キャッシュ画像の扱いが賢くなり、動作速度の向上や信頼性などが向上しています。
動かした方が分かりやすいと思いますのでムービーを作ってみました。
cacoo.elの説明については、github上のREADMEファイルに詳しく書いていますので、ぜひそちらも参照してください。
インストールと設定
必要なもの:
- 本体
- cacoo.el, cacoo-plugins.el
- 非同期ライブラリ
- deferred.el, concurrent.el
- Anything
- Cacooアカウント
- http://cacoo.com のアカウント
- CacooのAPIキー
- wget
- ImageMagick
通信には wget 、画像変換には ImageMagick を使います。多分Emacs使っているような人は大抵入っているのではないかと思います。
APIキーは、Cacooにログインして、「設定」>「APIキー」のページから取得できる文字列です。APIキーはCacooの図をAnythingで選択するために必要なものです。Cacooのアカウントが無くても画像のインライン表示自体は動きます。
anything.elはすでに皆さん入っていると思います。まだの人はるびきちさんのAnythingの記事とかを参考にしてみてください。
各elispファイルは、auto-install.elで以下の式を評価して入れるか、ダウンロードしてload-pathの場所に置いてください。
;; auto-installを使う場合 (auto-install-from-url "https://github.com/kiwanami/emacs-deferred/raw/master/deferred.el") (auto-install-from-url "https://github.com/kiwanami/emacs-deferred/raw/master/concurrent.el") (auto-install-from-url "https://github.com/kiwanami/emacs-cacoo/raw/master/cacoo.el") (auto-install-from-url "https://github.com/kiwanami/emacs-cacoo/raw/master/cacoo-plugins.el")
- ダウンロード
以下設定例です。
;; 基本設定 (require 'cacoo) ; cacooを読み込み (require 'cacoo-plugins) ; 追加機能 (setq cacoo:api-key "APIKEY") ; ←CacooのAPIキーを入れる(使わない人は無くてもOK) (global-set-key (kbd "M--") 'toggle-cacoo-minor-mode) ; Alt+「-」で切り替え ;; 追加設定 (setq cacoo:img-dir-ok t) ; 画像フォルダは確認無しで作る
そのほかの設定については、READMEファイルや cacoo.el の頭のカスタマイズ変数のあたりを参照してください。
使い方
絵を表示させたいなと思ったら cacoo-minor-mode を ON にします。(上の設定だと Alt+「-」)
テキスト中に以下のように記述して 'cacoo:reload-all-diagrams-command' (C-c , R) すると Cacoo の絵が入ります。
[img:https://cacoo.com/diagrams/6m4ATG1ddlUiHPqd-0FAF7.png]
'cacoo:anything-command' (C-c , I) すると、Cacooの絵(各シートも含めて)をAnythingで選ぶ画面になります。(上の動画参照)
貼り付けた図はローカルにキャッシュされます。キャッシュは現在のディレクトリ(バッファが保存されるディレクトリ)のなかの「.cimg」というディレクトリに保存されます。オリジナルの図とリサイズされた図が保存されます。
キャッシュがあればネットワークに接続にいきません。リロード 'cacoo:reload-all-diagrams-command' するとキャッシュを消して取りに行きます。リサイズもこのときに行われるので、サイズを変えたいときはリロードしてください。
機能・キーバインド
'cacoo-minor-mode' が ON の時に以下のキーバインドが使えます。メニューからも選べます。
バッファ全体に対して | |
---|---|
C-c , T | バッファのすべての図をテキストに戻す |
C-c , D | バッファのすべての図を表示する |
C-c , R | バッファのすべての図を取得し直す |
カーソール直後の図に対して | |
C-c , t | テキストに戻す |
C-c , d | 図を表示する |
C-c , r | 図を取得し直して表示する |
C-c , e | 図の編集画面を表示する(Cacoo) |
C-c , v | 図の詳細画面を表示する(Cacoo) |
C-c , V | ローカルの図を外部ビューアーで開く |
カーソールが含まれる図に対して | |
C-c , g | 図のリロード表示・テキストに戻すのトグル |
API / Cacooの機能に対して | |
C-c , I | Anythingで図を選択して挿入 |
C-c , N | 新規図の作成(Cacoo) |
C-c , l | 図の一覧(Cacoo) |
ナビゲーション、編集 | |
C-c , n | 次の図に移動 |
C-c , p | 前の図に移動 |
C-c , i | 図のマークアップを挿入 |
C-c , y | クリップボードのテキストを使って図のマークアップを挿入 |
その他 | |
C-c , C | キャッシュディレクトリを空にする |
表示できる図について
Cacoo 以外の図でも以下のような図を表示することができます。(もちろん編集はできません)
Web上の画像 | [img:http://example.com/zzz.png] |
ローカルの画像(絶対パス) | [img:file:///xxx/yyy/zzz.png] |
ローカルの画像(相対パス) | [img:zzz.png] |
また、プラグインによって動的に生成(org-babelが図になったイメージ)した画像を表示させることも出来ます。
画像取得時のエラーについて
画像の取得や変換中にエラーが起きた場合、該当箇所の色が変わります。また、マウスオーバーで短くエラーメッセージがポップアップで表示されます。
今後
今後の記事で以下の内容を予定しています。
上はLinuxで画面キャプチャが貼り付けられない問題が発生する場合があるのですが、これをChromeReplを使って可能にする方法を紹介します。方法自体はおそらく一般的なので、ローカルアプリとWebアプリを組み合わせる際に応用が利くと思います。
下は半年間いろいろ作ってきた経験をふまえて、非同期アプリの設計の方法についてのまとめを書こうと考えています。おそらく、EmacsでWindow間やバッファ間をまたぐアプリや、Emacsの外の世界と非同期にやり合うアプリでの一つの方針が示せるのではないかと考えています。まだ考えているだけですので、もし間に合えば開発環境勉強会などで議論やいろいろ教えていただければと思っています。
その他
Cacoo API への対応は昨年末には一通り出来ていたのですが、いろいろと手直ししていたり寄り道しているうちに時間がかかってしまいました。思ったよりもなかなか時間がないです。
cacoo.elは、手元ではmarkdownやhowmなどと組み合わせて、図を含めた文章の効率的な作成環境となっています。Emacsとの組み合わせは下手なローカル作図アプリよりも最高です。
有料プランだとPDFやSVGなどにより図のExportや印刷が可能ですので、もはや方眼紙Excelの出番は無くなってしまいました。また、Web上でマニュアルなどを作成される方は、画像ファイルの管理やわざわざアップロードする手間が無くなりますので、これまたすごく便利です。この機会にPayPal支払いの練習もかねてぜひどうぞ!!!
yasnippet や emacs lisp の小ネタなど
yasnippet は定型コードなどを素早く展開することが出来る汎用テンプレートフレームワークです。
普通の使い方
yasnippetのドキュメントは本家のページに良くまとまっています。elisp書ける人は全部読むとかなりsnippet力が上がると思いますので、ぜひ読んでみたらいいと思います。
あと、以下のページでもみんなテンション高くなってます。(もう3年くらい前の祭りですが。。。)
- yasnippetがすごい!!!!1112345! - Kentaro Kuribayashi's blog
- yasnippet.elをインストールしてみた
- yasnippet, anything-c-yasnippetのまとめエントリー - IMAKADO::BLOG
基本的に、「あ、いまsnippetひらめいた!」と思った瞬間に、 M-x yas/new-snippet で新規スニペット登録バッファを開いて、 C-c で保存・登録。込み入ったものだと何度かテストと書き換えをすると思います。
以前書いたスニペットの修正は M-x yas/visit-snippet-file (今いるバッファのスニペットから選ぶ)もしくは M-x yas/find-snippets (スニペットのディレクトリから探す)で出来ます。
ちなみに、yasnippetとzencodingを合わせると展開プレビューや補完が効きまくってHTMLがすらすら書けるようになるようですのでおすすめです。
組み合わせや干渉など
yasnippetが大量にあると覚えられなくなるので、anythingで絞り込むという方法があります。以下のページが参考になると思います。こちらはsnippetを選ぶだけでなく、新規追加などのアクションもあります。
auto-completeのソースにしてしまって、打った端からプルダウンで見せるという方法もあります。auto-completeに標準で含まれていますので、 ac-source に ac-source-yasnippet が入っていれば使えます。 auto-complete-config.el を確認してみてください。
yasnippetと干渉するものがいくつかあります。
flymakeとの干渉については antipopさんの記事 yasnippet関連の設定 - Kentaro Kuribayashi's blog に設定例があります。
auto-complete を展開中に無効にする設定を以下のgistに書いてみました。
基本的に yas/before-expand-snippet-hook と yas/after-exit-snippet-hook のhookで他の機能を一時停止したり再開させると良いと思います。
自分snippets
デフォルトではいくつかテンプレートがありますが、なかなか人の作った短縮コードは覚えられないものです。なので最初から用意されているテンプレートは参考程度にしておいて、自分で登録していって増やしていくのが良いのではないかと思います。
自分はあんまり数は多くないです。例えばelispは以下を登録しています。
展開元 | 展開先 | |
---|---|---|
defvar | da | (defvar |
internal | コメントで int | [internal] |
let | let | (let (( |
defun | df | (defun |
interactive | 非コメントで int | (interactive) |
lambda | la | (lambda () ) |
lexical-let | ll | (lexical-let (( |
let が「(let ((」になるだけでもかなり違います。ちなみに、これはimakado君に教えてもらいました。
上の例では文字が入るだけではなくて、いろいろ仕掛けが入っています。例えば、defvar, defun は、展開時に前後のコードを見に行って、それらしいプレフィクスを探してきて自動で入れます。さらに、引数の文字列からdocstringを強制的に生成するお節介機能もあります。
defun展開の様子
defun の yasnippet:
# name : defun template # group : defun # key : df # contributor : kiwanami # -- (defun ${1:`(save-excursion (let ((re "(def\\\\(un\\\\|var\\\\)[ \t\n]*\\\\([a-z0-9]*[:-]\\\\)")) (cond ((or (re-search-backward re nil t) (re-search-forward re nil t)) (match-string 2)) (t ""))))`}${2:func-name} (${3:args}) "$2 ${3:$(mapconcat 'upcase (split-string (replace-regexp-in-string "&[a-z]+" "" yas/text)) " \n")}" $0 )
このように、かなり本気の elisp が書けます。参考になればと思います。
注意点として、文字列の中のバックスラッシュが一度展開されてしまうようなので、正規表現を書く場合はバックスラッシュを多めに書く必要があります。
デバッグが難しいので、小さなものから書いていったり、やっぱり yasnippet のコードを呼んだりしてブレークポイントを仕掛けるのが早いです。
はまり所としては、初期値が無いフィールドが隣接していると、フィールドが消えてしまいます。「$0」とかは要注意です。これはフィールドの位置をマーカーオブジェクトで印をつけていて、そのため開始終了位置が重なってしまうとマーカーがくっついて離れなくなってしまうからです。実装上の制限だと思いますので、フィールドが消えてしまう現象で困った場合は、スペースなどで間をあけると良いと思います。
いくつかはここのgistにおいています。
その他テク
やっぱり、yasnippetの応用技の解説はそれほど多くないのですがいくつか紹介してみます。
ありえる社の深町さんが、昨年の Software Design で yasnippet の記事を書かれています。(ごめんなさい。まだ読んでないです!><)
るびきちさんは、連続して展開しやすくする工夫を紹介されています。
yasnippet.elを256倍にパワーアップ!連続展開と条件分岐テンプレートを使おう - (rubikitch loves (Emacs Ruby CUI Books))
やっぱり、yasnippetのすごさは目の前で使っているのを見るのが一番いいと思います。ということで、近所のEmacs勉強会にぜひ参加してみましょう。
おまけ: let の * を切り替える
lisp系で let とかの中身を書いているときに、「*」を付けたり消したりするのを思い出して行ったり来たりすることが多かったので、一発でトグルできる機能を書いてみました。
let, lexical-let の変数宣言リストの中(下のコードのhereのあたり)で、 toggle-let-astah を実行するとletの「*」を切り替えます。
(let ( ; here (a 1) ; here (b 2) ; here ) body...)
自分の所では C-8 に割り当てています。(US配列で*があるのと、C-[数字]を使わないので)
S式を見ながらletを探しに行きますので、予想もしないところのletを書き換えたりはしないはずだと思います。正規表現を書き換えれば、他のものにも使えると思います。
以上。小ネタでした。