ハンタイのハンタイの〜

月刊少女野崎くんED ウラオモテ・フォーチュン には

キライの ハンタイの ハンタイの ハンタイの ハンタイの
そのさらにハンタイの
キモチを伝えるのって 何だか難しい

という歌詞が出てきます.

これをEmacs Lispで解いた という記事があり、先日僕はRubyで解きました

語順がああああ

通常のelispでは

(setq キライの nil)

(defun ハンタイの(p) (not p))
(defun そのさらにハンタイの(p) (not p))
(defun キモチを伝えるのって(p) (if p 'スキ 'キライ))
(defun 何だか難しい(p) p)
(defun ハンタイは?(p) (if (not p) 'スキ 'キライ))

(何だか難しい
 (キモチを伝えるのって
  (そのさらにハンタイの
   (ハンタイの
    (ハンタイの
     (ハンタイの
      (ハンタイの キライの))))))) ; => スキ

のように語順が逆になってしまいますが、Rubyでは

# -*- coding: utf-8 -*-
class Object
  def ハンタイの() !self end
  def そのさらにハンタイの() !self end
  def キモチを伝えるのって() self ? :スキ : :キライ end
  def 何だか難しい() self end
  def ハンタイは? () !self ? :スキ : :キライ end
end

キライの=false
キライの.ハンタイの.ハンタイの.ハンタイの.ハンタイの.そのさらにハンタイの.キモチを伝えるのって.何だか難しい
# => :スキ
キライの.ハンタイの.ハンタイの.ハンタイの.ハンタイの.ハンタイの.ハンタイの.ハンタイは?
# => :スキ

と、歌詞の語順を保持できます。

そこでthreading macroですよ!

では、elispで語順を保持できないのかと思ったところ、
頭にthreading macroというのが思い浮かびました。

threading macroというのは、最初はなかなか理解できなかったのですが、
一言で言えば
Lisp的メソッドチェーン
だとわかりました。

多くのオブジェクト指向言語では メソッドチェーン によって、
前のオブジェクトから順々にメソッドを実行して
パイプラインのように処理が記述できます。

elispでメソッドチェーンのように記述できるのですよ!!!

では使ってみましょう。

threading macroは dash.el というMELPAダウンロードランキングNo.1の
リスト処理ライブラリに含まれています。

今では多くのパッケージで使われているので、
それなりの数のパッケージをインストールしているのならば、
おそらく自動的にインストールされていると思います。

(require 'dash)

しても
Cannot open load file
と怒られる場合は
M-x package-install dash
でインストールしてください。

汎用的な-->

threading macroで一番汎用的なのは「-->」というマクロです。

使い方は

(--> X &rest FORMS)

と記述し、XにFORMSの最初の関数を呼び出し、
その結果を次の関数呼び出しに使います。

FORMSにおいて it と指定した部分に
前の引数が渡ります。

Lispの通常の記法と比べてみれば、
処理の流れがわかりやすくなったでしょう(特にOOな人にとって)。

(require 'dash)
;;; 文字(整数)のリストから文字列リストを生成し、大文字化する
(mapcar 'char-to-string '(?a ?b))                  ; => ("a" "b")
(--> '(?a ?b) (mapcar 'char-to-string it))         ; => ("a" "b")
(mapcar 'upcase (mapcar 'char-to-string '(?a ?b))) ; => ("A" "B")
(--> '(?a ?b) (mapcar 'char-to-string it) (mapcar 'upcase it)) ; => ("A" "B")
;;; ネストされたリストの[1][1]を得る
(nth 1 (nth 1 '((1 2 3) (4 5 6))))             ; => 5
(--> '((1 2 3) (4 5 6)) (nth 1 it) (nth 1 it)) ; => 5

では、

(何だか難しい
 (キモチを伝えるのって
  (そのさらにハンタイの
   (ハンタイの
    (ハンタイの
     (ハンタイの
      (ハンタイの キライの))))))) ; => スキ

を-->で書き換えてみましょう。

(--> キライの (ハンタイの it) (ハンタイの it) (ハンタイの it) (ハンタイの it)
     (そのさらにハンタイの it) (キモチを伝えるのって it) (何だか難しい it)) ; => スキ

ほれ!見事に語順が保持されました!!!

短縮!

でも、itが冗長ですね。

実は-->では (FUNC it) はFUNCと記述できます。

(--> キライの ハンタイの ハンタイの ハンタイの ハンタイの
     そのさらにハンタイの キモチを伝えるのって 何だか難しい) ; => スキ

やりました!!elispでも歌詞そのまま記述しても問題なくなりました(笑)

後半の歌詞も同様に!

(--> キライの ハンタイの ハンタイの ハンタイの ハンタイの
     ハンタイの ハンタイの ハンタイは?) ; => スキ

短縮形「->」と「->>」

ついでに-->の短縮形とも言える->と->>も用意してあります。

->は (FUNC it args) を (FUNC args) と記述できるようになり、
->>は (FUNC args it) を (FUNC args) と記述できるようにします。

(--> '(2 3 5) (append it '(8 13)))      ; => (2 3 5 8 13)
(-> '(2 3 5) (append '(8 13)))          ; => (2 3 5 8 13)

-->の最初の例はすべてitが最後なので以下のように書き換えられます。

(->> '(?a ?b) (mapcar 'char-to-string)) ; => ("a" "b")
(->> '(?a ?b) (mapcar 'char-to-string) (mapcar 'upcase)) ; => ("A" "B")
(->> '((1 2 3) (4 5 6)) (nth 1) (nth 1)) ; => 5

(FUNC)はFUNCと書けるので、-->と同様に

(-> キライの ハンタイの ハンタイの ハンタイの ハンタイの
    そのさらにハンタイの キモチを伝えるのって 何だか難しい) ; => スキ
(->> キライの ハンタイの ハンタイの ハンタイの ハンタイの
     ハンタイの ハンタイの ハンタイは?) ; => スキ

リスト処理の関数は最後の引数にリストを取るので
->>の活躍の機会が多くなります。

;;; ->>でalistのalistもお手のもの!
(->> helm-type-attributes (assoc-default 'file) (assoc-default 'action))
;;; => (("Find file" . helm-find-many-files)
;;;     ("Find file as root" . helm-find-file-as-root)
;;;     ("Find file other window" . find-file-other-window)
;;;     ("Find file other frame" . find-file-other-frame)
;;;     ("Open dired in file's directory" . helm-open-dired)
;;;     ("Grep File(s) `C-u recurse'" . helm-find-files-grep)
;;;     ("Zgrep File(s) `C-u Recurse'" . helm-ff-zgrep)
;;;     ("Pdfgrep File(s)" . helm-ff-pdfgrep)
;;;     ("Insert as org link" . helm-files-insert-as-org-link)
;;;     ("Checksum File" . helm-ff-checksum)
;;;     ("Ediff File" . helm-find-files-ediff-files)
;;;     ("Ediff Merge File" . helm-find-files-ediff-merge-files)
;;;     ("Etags `M-., C-u tap, C-u C-u reload tag file'" . helm-ff-etags-select)
;;;     ("View file" . view-file)
;;;     ("Insert file" . insert-file)
;;;     ("Add marked files to file-cache" . helm-ff-cache-add-file)
;;;     ("Delete file(s)" . helm-delete-marked-files)
;;;     ("Copy file(s) `M-C, C-u to follow'" . helm-find-files-copy)
;;;     ("Rename file(s) `M-R, C-u to follow'" . helm-find-files-rename)
;;;     ("Symlink files(s) `M-S, C-u to follow'" . helm-find-files-symlink)
;;;     ("Relsymlink file(s) `C-u to follow'" . helm-find-files-relsymlink)
;;;     ("Hardlink file(s) `M-H, C-u to follow'" . helm-find-files-hardlink)
;;;     ("Open file externally (C-u to choose)" . helm-open-file-externally)
;;;     ("Open file with default tool" . helm-open-file-with-default-tool)
;;;     ("Find file in hex dump" . hexl-find-file)
;;;     ("Execute Shell Command" . helm-c-do-shell-command))

threading macroを使って複雑な処理やデータ構造も
スタイリッシュに扱いましょう!!

オブジェクト指向的な記法で書けなくて悩んでいる人にとって、
threading macroは福音ではないでしょうか。

本日もお読みいただき、ありがとうございました。参考になれば嬉しいです。