Emacs Lispで感じた書き散らすプログラミング体験

この記事は、はてなエンジニア Advent Calendar 2023の2024年1月16日の記事です。

昨日は id:fxwx23 さんによる「Simulator.app の「Stay On Top」をキーボードショートカットで切り替える」でした。 かゆい所に手が届かないこと、よくありますよね。その際にシュッとスクリプトを書いたりして不便を解消する姿は見習いたいと思います。

始めに

自分は普段テキストエディタEmacs を使っています。 EmacsEmacs Lisp というプログラミング言語でエディタの設定を記述できますが、今まで設定を記述する以外の目的で Emacs Lisp を書いたことがなく、プログラムっぽいプログラムを書いたことはありませんでした。

ふとこのことが気になって、年末年始の休みに簡単なプログラムを作ってみました。

実際に書いてみると、他の言語ではあまり体験したことのないようなプログラミング体験が出来ました。 ここでは Emacs Lispプログラミング言語として書いたときに得たプログラミング体験と、その感想について書いています。

Emacs Lisp でのプログラミング体験

LTSV 文字列をリストに変換する関数の実装を進めることを例として見ていきます。 以下は実装の一例です。Lisp について知らなくても、雰囲気でも何をやっているかがわかると思います。

(defun parse-ltsv-string (ltsv-string)
  "引数の LTSV の文字列を リストのリストにする ((key1 value1) (key2 value2))."
  (let ((values '()))
    (dolist (pair (split-string ltsv-string "\t"))
      (let ((key-value (split-string pair ":")))
        (push key-value values)))
    values))

(parse-ltsv-string "key:value\tname:miki_bene")
;; => (("name" "miki_bene") ("key" "value"))

この関数の実装過程で 4行目の (split-string ltsv-string "\t") の返り値が実際にどんな値が返るのか、確認してみたかったとします。 他の言語なら REPL を起動したり、ブラウザ上で実行できる Playground で試したりするところですが、Emacseval-last-sexp *1というコマンドを使うことで Emacs 上で実行が出来ます。

(defun parse-ltsv-string (ltsv-string)
 ...
)

;; 以下のコードの括弧の末尾にカーソルを配置し、
;; C-x C-e キーバインドで eval-last-sexp コマンドを実行する
(split-string "key:value\tname:miki_bene")
;; => ミニバッファに ("key:value" "name:miki_bene") が表示される

このように、編集しているプログラムから全く離れずに書いたコードを実行できます。

また、eval-last-sexp はどこでも実行できるので、例えば関数の中で全然違う処理をいきなり書いて実行することも出来ます。

(defun parse-ltsv-string (ltsv-string)
  ...

  ;; dolist でループ処理を行う際の動きを見たいので
  ;; *Messages* バッファに文字列を出力する
  (dolist (pair (split-string "key:value\tname:miki_bene" "\t"))
    (message "%s" pair)) ;; ここで C-x C-e コマンドを実行する
  ;; => *Messages* バッファに
  ;; key:value
  ;; name:miki_bene
  ;; nil
  ;; が出力される
  
  ;; 以下は書いている途中の処理
  (let ((values '()))
  ...
)

実は最初のコードもコマンドラインで実行した結果ではなく、Emacs 上で評価した結果を貼りつけているだけでした。

感想

例のように、Emacs Lisp では今書いているプログラムからほとんど離れずに処理を試したり、関数を実行できます。

実際に Emacs Lisp を書いていた際は、関数定義中の処理の中に具体的な値を入れて試す→動くことを確認したら値を変数に置き換えて続きを書き進める→また確認したくなったら具体的な値を入れて試す……を繰り返しながら実装を進めていました。 この繰り返しをすると気づいたらプログラムが出来上がっており、書いたプログラムが動くことの確信がほぼ持てているといった状態になっていました。

他のプログラミング言語で実装を進める際も似たようなことは行っているはずですが、Emacs Lisp の場合は処理の記述と実行の間がとてもシームレスで、絶え間無くプログラムを書き進められる感覚がありました。

もちろん Lisp 自体でつまずくことはあったので、言うほど絶え間無くではありませんでした。 ただ、書いてすぐに実行してフィードバックが返る素朴な楽しさがありました。

関数の中で全然違う処理を書いて実行できるのは滅茶苦茶だなと思いつつも、好きな場所に好きに書いても実行できる感覚は真っ白な自由帳に落書きを書いているみたいで、それも楽しくありました。

他のエディタで類似の機能はないのか

Emacs Lisp 以外で同様のものはないかと探してみると、VSCode の拡張に似た機能がありました。 選択した範囲を REPL に流し込み実行できるといったもので、割と感覚は近いかもしれません。

ただし Emacs Lisp の場合は評価した結果が残るので、以前評価されていれば選択していない部分の関数定義やライブラリ読み込みも使えるのは大きな違いかもしれません。 評価が残ってしまうのは意図しない結果になる時もありますが、その場で実行する場面においては基本利便性の方が高いだろうと感じています。

まとめ

Emacs Lisp を書いて、他の言語ではあまり体験できない実装体験が出来ました。 普段の業務では Perl や Go, TypeScript を書いています。これらの言語が悪いわけではない(むしろ静的型付けの方が好ましい)ですが、普段はあまり感じない書き散らす楽しさを久しぶりに感じました。

たまにはこういった自由帳のような言語で遊んでみるのもよいかもしれません。

はてなエンジニア Advent Calendar 2023、明日は id:hagihala さんです!

Emacsのlsp-modeが遅いときはlsp-doctorを見てみるといい

emacs-lsp.github.io

タイトルと↑のリンクでFAなのだけれど、EmacsのLSPクライアントであるlsp-modeで補完などの動作が遅いなと感じたときは M-x lsp-doctor とやると遅い原因を列挙してくれる。

Emacsのデフォルト設定値ではlsp-modeの利用に最適ではない場合があるのは、言われてみればその通りなのだけど完全に失念していた。 lsp-mode側でよしなに書き換えてしまってもいいのに……と思ったけど、それをやるのは流石に思想の違いがありそう。

自分の場合はGCが発生するメモリの閾値gc-cons-threshold と、一度の読み取り操作でサブプロセスから何バイト読み取るかの read-process-output-max の値を調節すると、補完候補が出る速度が体感でわかるほど早くなった。 元々候補が出るのに一瞬ひっかかるなと感じる程だったので、明らかに体験が良くなった。

lsp-mode 遅いなと感じている時は試してみるとよさそう。

※ 追記 v7.0 までは lsp-diagnose って名前だったらしい

github.com

Emacsのモードラインの時刻表示を秒単位するにはdisplay-time-intervalでやる

タイトルのままのことがやりたいだけ.

以下の設定でできます.

(setq display-time-interval 1) ;; こいつで設定する. デフォルト値は60秒で, 今だと1秒毎に更新
(setq display-time-string-forms
  '((format "%s:%s:%s" 24-hours minutes seconds)))
(setq display-time-day-and-date t)
(display-time-mode t)

これでEmacsでモードラインに時刻を表示させる display-time-mode をちょっと快適にできました.

僕は時刻表示をするときはいつも秒まで表示するタイプなんだけど, Emacsで秒単位を表示させると時刻の更新間隔が1分毎なので困まった. これだと秒を表示する意味がない.

普通にググると単純に時刻を表示するための display-time-day-and-datedisplay-time-mode の記述しか出てこない. やり方わかってから見つけたけどEmacsWikititle-tile.el の部分にちょろっと書いてあったりする.

EmacsWiki: title-time.el

結局ググっても全然出てこないので, describe-variabledisplay-time-mode を調べて time.el の存在を知った. そこからそれっぽい display-time-update, display-time-event-handler を見つけ, display-time-interval を発見したという流れ.

Emacs設定情報結構ググってしまいがちだけどわからければ describe-variable で調べて定義周辺のコードを読んでしまえばわかるというのは良い体験だった.

これからもEmacs少しづつ便利にしていきたい.

追記:

emacs display-time-interval で調べたら日本語の記事がかろうじて出てきた(というか英語記事も全然出ないのだが……). このサイトがめちゃくちゃ良くてスラドなんですよ. スラド最高.

srad.jp

Emacs実践入門 出版記念イベントに参加してきた

Emacs実践入門 出版記念イベント に参加してきた.

5年前に出たEmacs実践入門の改訂版が出たことによる出版記念イベントで, 内容としては著者の 大竹智也 さんの基調講演や, 自分のエディタさばきを披露する『エディタの鉄人』, 様々な発表者の方の発表などがあった. 暗黒美無王でおなじみの@ShougoMatsuさんの実況・解説があって, ときどき「Vimだとこうですね」と間にはさんでいて面白かった.

盛り上がったのが『エディタの鉄人』で, https://www.gnu.org/software/emacs/history.html のページからEmacsのリリースリストをコピペしてきて, これに対して

  1. - を タブに変換する
  2. <li></li> で各行を囲む
  3. Markdownのテーブルにする

という操作を前で披露してもらうというもの.

操作一つをとっても一人一人でやり方が違っていて, - を複数選択してマルチカーソルでタブにする人や, replace-regex で置換する人, <li> は矩形選択して </li> は置換を使う人, マルチカーソルで <li></li> を挿入する人と様々だった.

ただMarkdownのテーブルに直す操作では, それぞれの方法で | を挿入してから見た目を整えるときにorg-modeの機能の一部をMarkdownの編集に利用する, といったものが共通していてorg-modeを全然利用していない自分としては目から鱗だった.

他にもorg-modeの凄さを感じる瞬間が多く, 他人と共有するMarkdownやPDF, docxをorg-modeで書いてから変換させたり, 発表スライドをorg-modeで作ってきている方もいてorg-mode万能説を感じた.

他人がEmacsを使う様子を見ていると自分はまだまだEmacsを活用しきれていないなと思ったけど, 今後どんな風にEmacsを拡張していくかの参考になったので他人の使う様子を見れたのは良かった.

またこのような機会があれば是非参加したいし, 今度は自分のエディタを披露できるくらいになりたいと思った良いイベントだった.

当日の様子はこちらをどうぞ. twitter.com

Elpyを使ってPython用Emacsの設定を書いた

大学4年生になり研究でPythonを使う必要が出てきたので, EmacsPython用の設定を追加した. 普段なら他の人の設定をコピペするだけなのだが, 今回利用したパッケージを使った例が少なかったのでまとめることにした.

前提条件

Python2系が必要なときはvirtualenvを使うけど, 3系前提ならvenvだけで良さそう*1.

前提と同じPython3 + venvを入れるには以下の記事が参考になる.

pyenv + venvでPython3環境を構築する - $shibayu36->blog;

利用したパッケージ

elpyはEmacsPythonIDE化するパッケージ.

書いた設定

(el-get-bundle! python-mode)
(setq auto-mode-alist (cons '("\\.py\\'" . python-mode) auto-mode-alist))

(el-get-bundle! elpy
  (elpy-enable)
  (setq elpy-rpc-backend "jedi")
  (add-hook 'elpy-mode-hook
    '(lambda ()
       (auto-complete-mode -1)
       (define-key company-active-map (kbd "C-n") 'company-select-next)
       (define-key company-active-map (kbd "C-p") 'company-select-previous)
       (define-key company-active-map (kbd "<tab>") 'company-complete))))

使い方

  • プロジェクト環境に依存パッケージをインストール.
$ source bin/activate
$ pip install jedi もしくは rope
$ pip install flake8 importmagic autopep8 yapf
  • Emacsでプロジェクトディレクトリを開いて M-x pyvenv-activate をする. どの仮想環境を利用するか選択するので, プロジェクトディレクトリを指定して選択.
    • プロジェクトの環境が有効になり補完やLintが効くようになる.
  • 完了
    • 何かおかしかったら M-x elpy-config で設定を確認できるのでそれを見ながら修正する.

補完のバックエンドをjediropeで選択できる. どっちを選べばいいかはelpyの公式のWikiに書いてある.

雑感

よくあるブログエントリだとjediを入れて補完が出来るようにするのが多いけど, Python仮想環境のことを考えていなかったりvirtualenvを使っていたりで千差万別なので, 今回自分用でまとめてみた.

Pythonは書き始めたばかりなので, IDEの機能や周辺のLint・リファクタリングツールを全然使いこなせていない. 手持ち無沙汰感は否めないけどよしとする.

設定に関してだと el-get-bundle! の辺りは各自の使っているパッケージ管理ツールに適宜読み換えればいいと思う.

elpy-mode-hook にくっついている設定はAuto-Completeを無効にし, Company-Modeのキーバインドを最低限設定するもの. 普段からCompany-Modeを使っている人必要なさそうだけど, 自分は普段Auto-Completeを使っているのでPythonを書くときだけCompany-Mode を有効にするという形を取った.

Auto-Completeを使う方法もあるのかもしれないけどそこまで調べていないのでひとまずは現状でいいかという気持ち.

より詳しく, 正確にEmacsPythonの設定をするならEmacsWikiを見ると良さそう.

EmacsWiki: Python Programming In Emacs

参考記事

*1:むしろvenvのみの方が余計なものを考えなくて良いと思う. 止むに止まれぬ事情があるときはvirtualenvを入れよう

Emacsの設定をぶっ壊してしまったので作り直してる (あとGitHubのProjectsの機能を試してる)

タイトル通り。Emacsの設定を変更したらぶっ壊れてしまったので難儀してます。

github.com

加えた変更は今まで一つの init.el に設定を書いていたのを、init-loaderというパッケージを使って細かくファイルを分割して読み込もうということをしたかった。

ファイルの分割は上手くいったんだけど、他のパッケージの読み込みがされなくてひどいことになってる。

当然gitで管理はしているからロールバックは出来るけど、これを機に溜まっていた不満を一気に解消したいので踏ん張ろうみたいな気持ちになってる。

それと同時にGitHubの新機能のProjectsを試してる。Issue管理すら不要だけど折角なので使ってみてる。

パッと触わってみた感じだとIssueとラベル管理で同等のことは表現できるだろうけど、カンバンを使えるという点ではいいのかなという印象。個人で使ってると全然威力が発揮されなさそうなのでまあそうだよねという感じ。

見方によってはものすごく些細なIssue未満やラベルが無限に増えていくのをProjectsでnoteとカテゴリ分けをすることで解消できそうだけどこれも個人で使ってるからこう感じたのかも。

個人でカンバンを活用する知見を教えてほしいです。