7月10日、yummymelon.comが「In Emacs, Everything Looks Like a Service」と題した記事を公開した。EmacsをREST APIクライアントとして構築する実践例を通じて、「あらゆるものをサービスとして見なす」というアーキテクチャ的思考を紹介している。
VSCodeやNeovimが台頭する中でもEmacsユーザーが一定数存在し続けるのは、Emacsが「エディタ」の枠に収まらないからだ。著者はその理由を一言で説明する。Emacsは、OSカーネルより上のレイヤーでアプリケーションを統合・調整できる能力を持ち、ファイルシステムやネットワークへの直接アクセスと、他プログラムの起動を組み合わせられる。この2つの組み合わせが、Emacs内で即席のクライアントを作ることを「日常的な行為」にする。
Emacsが「OS的」と言われる本当の理由
「EmacsはOS」という慣用句は古くからあるが、著者はその比喩を構造的に分析する。クライアント-サーバーモデルにおいて、クライアントが担う責務は大きく3つだ。
- UI:ユーザーインターフェース
- クライアントエッジ:サービスとの通信サブシステム(ネットワーク等)
- ローカルデータベース:サーバーと交換・同期するデータの管理
Emacsはこの3つすべてに対応するライブラリを標準で持っている。さらに重要なのがElisp(Emacs Lispの略。Emacs組み込みの拡張言語)の動的な性質だ。実行時に高度な即興処理が可能で、Elisp関数とシェルコマンドを組み合わせた複雑な統合も、特定のフレームワークに縛られずに実現できる。
67行で作る天気クライアント——外部依存ゼロ
著者が具体例として取り上げるのが、wttr.inを使った天気コマンドの実装だ。wttr.inはコンソール向けの天気予報WebサービスでJSON出力にも対応しており、REST APIの学習素材としても使いやすい(GitHubリポジトリ)。
実装の流れはシンプルだ。ユーザーがミニバッファ(Emacs下部の入力欄)で場所を入力すると、wttr.inのURLを構築してHTTPリクエストを発行し、返ってきたJSONをElispのハッシュテーブルに変換して必要な値を表示する。クリップボード相当のkill-ringにも結果を保存するため、他のバッファへの貼り付けも即座にできる。
中核となるfetch-json-as-hash-table関数では、url-retrieve-synchronously(Emacs標準のHTTPクライアント関数)でリクエストを行い、json-parse-bufferでJSONをパースする。メモリリークを防ぐためunwind-protectでバッファを確実に解放している点も実用的だ。
(defun fetch-json-as-hash-table (url)
"Fetch URL with expected JSON response and return a `hash-table'."
(let ((data-buffer (url-retrieve-synchronously url)))
(if (not data-buffer)
(error "Failed to fetch data from %s" url)
(unwind-protect
(with-current-buffer data-buffer
(goto-char url-http-end-of-headers)
(json-parse-buffer :object-type 'hash-table))
(kill-buffer data-buffer)))))
clocで計測した結果、wttr.el全体のコード量はわずか67行。外部ライブラリへの依存ゼロで、Emacs標準ライブラリだけがREST APIクライアントとして機能する。完全な実装はwttr.el(GitHub Gist)で公開されている。
さらに薄くする選択肢——シェルコマンドを「サービス」にする
67行でも多いと感じるなら、ネットワーク処理とJSON解析をPythonスクリプト(weatherコマンド)に任せてしまう方法もある。その場合、Elisp側はshell-command-to-stringを呼ぶだけの数行に収まる。
(defun weather (location)
"Call weather script with LOCATION and show result in minibuffer."
(interactive "sWhere (default: local): ")
(let* ((weather-cmd "weather")
(cmd (if location (format "%s %s" weather-cmd location) weather-cmd))
(result (shell-command-to-string cmd)))
(kill-new result)
(message result)))
この構成では、シェルコマンド自体が「サービス」として機能する。curlやjqを外部で使い慣れている開発者にとっても、その処理をEmacsからラップするだけで「Emacs内完結の道具」に変換できる。Elispがネットワーク処理を担うかどうかは実装の選択にすぎず、コマンドラインツールをサービスとして再解釈するという発想こそがEmacsクライアント設計の本質だと著者は強調する。
この視点は、Emacs以外の文脈にも応用できる。シェルスクリプトやMakefileで複数ツールを連携させてきた開発者であれば、「あらゆるコマンドはサービスのエンドポイントである」という読み替えは自然に腑に落ちるはずだ。
詳細はIn Emacs, Everything Looks Like a Serviceを参照していただきたい。