template-replaceの更新とキーワード引数

すっかり忘れそうになっていた賃貸契約の更新に行ってきました。記憶と住所を頼りに不動産屋に行ったら、移転していたという悲劇。「通知に書いてあったんですが・・・」と言われ、必要書類とかしか見てなかった自分にorzでした。
そういえば初めて中野坂上という場所に行きました。丸の内線も二年くらいぶりに乗りましたね。・・・でもそれより、京王線→丸の内線の乗り換えで、新宿駅のダンジョンっぷりを味わう羽目になりました。新宿がさらに嫌いになる罠です。

「それ、yasnippetでいいんじゃね?」が合言葉の拙作 elisp のtemplate-replaceを更新しました。今回の更新ポイントは、

  • major-mode別にテンプレートを登録できるようにした。
  • template-function要素を追加して、format以外での置換ができるようにした。
  • テンプレートの作成が激しく面倒だったので、作成補助関数を作成した。

元々できるようにしておけや、という変更ばっかですね。

elispはこちらより。→http://github.com/derutakayu/template-replace/tree/master/template-replace.el

実際にどんな感じかというと、以下のような感じです。

;; c++-mode用
(template-replace-add-mode-alist 'c++-mode
 (template-replace-make-template "s"
                                 '(lambda () (bounds-of-thing-at-point 'symbol))
                                 "static_cast<%s>(%s)"
                                 :inputs "type to cast: ")
 (template-replace-make-template "c"
                                 '(lambda () (bounds-of-thing-at-point 'symbol))
                                 "const_cast<%s>(%s)"
                                 :inputs "type to cast: ")
 (template-replace-make-template "r"
                                 '(lambda () (bounds-of-thing-at-point 'symbol))
                                 "reinterpret_cast<%s>(%s)"
                                 :inputs "type to cast: "))

;; どこでも有効なtemplate
(template-replace-set-alist
 (template-replace-make-template "wrap"
                                 '((lambda () (bounds-of-thing-at-point 'symbol))
                                   (lambda () (cons (save-excursion
                                                      (re-search-backward "(" nil t)
                                                      (point))
                                                    (save-excursion
                                                      (re-search-forward ")" nil t)
                                                      (point)))))
                                 "(%s %s %s)"
                                 :tmpl-func '(lambda (template pat &rest inputs) (format template (car inputs) pat (cadr inputs)))
                                 :inputs ("wrap function: " "cdr value: "))

キーワード引数の存在は知っていましたが、今回初めて使ってみました。ちなみに↑の例はあまり意味がありません。例示するためだけに作成してみました。

ところで、私も実験してみて知ったんですが、elisp 標準のキーワード引数の指定を行う &key って、通常意図した意味で利用できないんですね。

(defun test (arg1 &key key1)
  (format "%s %s" arg1 key1))

(test "hoge" :key1 'huga) ; => "hoge huga"

;; この場合、キーワードを指定しない場合にはエラーとなる。
;; 複数設定する場合、それぞれ&keyを引数名の前に付与する。
(test "hoge") ; => error!

;; &optional の後に指定すると、指定しなくてもOKになります。
(defun test (arg1 &optional &key key1)
  (format "%s %s" arg1 key1))

(test "hoge") ; => "hoge nil"
(test "hoge" :key1 's) ; => "hoge s"

複数のキーがある場合、途中のキーを飛ばすとエラーになります。
キーの順番を変えて引数を渡しても、引数の順番通りにただ引渡されるだけになります。
どういうことかというと、

(defun test (arg1 &key key1 &key key2)
  (format "%s %s %s" arg1 key1 key2))

(test "hoge" :key2 'huga :key1 'foobar) ; => "hoge huga foobar"
;; ↑順番を変えても、キーワードと一致しているわけではない。

;; elispの&keyだと意味があまりないです。なので、clパッケージのdefun*を使います。

;; &keyの後の引数は全てキーワード引数になります。
(defun* test (arg1 &key key1 key2)
  (format "%s %s %s" arg1 key1 key2))

(test "hoge" :key2 'huga :key1 'foobar) ; => "hoge foobar huga"
;; ↑ちゃんと意図したとおりになっている。

(test "hoge" :key2 "ho") ; => "hoge nil ho"
;; ↑入力しないキーワードがあるとnilになる

キーワード引数を使う場合はdefun*を使うようにします。それよりもヘルプを見る方が先ですよね。

一応テストが通ることは確認していますが、もし試された方で不具合があればコメント等でご連絡頂ければ幸いです。
でもやっぱり「yasnippetでいいじゃん」になるような気がします。