xmonad-oid on emacs & more functional emacs lisp | 関数型lt大会
DESCRIPTION
関数型LT大会 @クックパッド, 東京 2014/05/11 の発表スライドです.TRANSCRIPT
XMonad-oid on Emacs &More functional Emacs
Lisp岡田 健 (keno)
自己紹介
岡田 健 (keno, Twitter: @keno_ss)
京都大学 数理解析研究所
Lisper, Schemer
最近は Haskell に興味がある ( 質問していいですか )
けど実際は Emacs Lisp ばっかり書いてる
Emacs Lisp って不便な言語ですよね
作ったもの
個人的テーマ「 ( 最小の性能の犠牲で ) 読みやすく書きやすくデバッグしやすい Emacs Lisp の環境を整備しよう」
ewm.el (EmacsWMonad; XMonod-oid on Emacs)
debug-print.el (Gauche’s nice printf-debugging)
ERFI (SRFI in Emacs Lisp)
hairs.el (Haskell influenced record syntax)
次は R7RS の define-library 的な仕組みが欲しい ( 名前空間がないので )
今日の話
XMonad-oid on Emacs
ewm.el
More functional Emacs Lisp
ERFI の紹介
hairs.el の紹介
XMonad-oid on Emacs
そもそもこれがやりたくて( 作り直したくて )
色々整備してる
今日は時間ないので無理興味ある人は個人的に
More functional Emacs Lisp
Lisp って関数型なの ?
Lisp
No (by “Let Over Lambda”).
Scheme
Maybe yes.
参照透明性は言語では保証されないけど書き方としては推奨されている
Common Lisp
知らない
CLer も不満らしい ? ( 深町英太郎「誰向けかわからない Common Lispでの関数型プログラミング入門とその未来」 )
Emacs Lisp は関数型 ?
Definitely no.
dynamic binding (not lexical binding)
let 内で lambda 使うときは注意しないとデバッグできないバグが
Emacs Lisp は CL 寄りな Lisp ( でも「 cl.el 使うな」って言われる )
末尾呼び出しの最適化がない (CL 処理系の多くでは使えるけど )
ループは while か CL 系の loop とか do-times とか
函数が破壊的版しかないことが多い
append-map! (mapcon) はあるけど append-map がない
けど最近は (emacs 24.3)
lexical-binding や closure が使えるように ! (>= 24)
cl.el が cl-lib.el になって ( 一応 ) 使ってもいいことになった
けど cl.el でできたことが一部できなかったり面倒だったり
gv.el (generalized variables; 汎変数 ) の仕組み ( 高階函数 )
pcase.el (ML-style pattern-matching macro for Elisp)
でもまだ足りない . 動き始めたばかり .
ライブラリ書いた( 書いてる )
ERFI
えるふぃ ( ポケモンではない )
Scheme の標準的なライブラリ SRFI(+Gauche の拡張 ) の Emacs版 ( にできたらいいな ) とか既存のイディオム ( 覚えられんし読めん )の改善とか
SRFI 1: リストライブラリ
SRFI 2 26 61 67: and-let* cut cute cond case ecase
SRFI 5: 名前付き let, 末尾再帰の最適化
「 Elisp は CL じゃねぇ ! 」と言ってるしこういうのあってもいいかなと
named let
普通の変数束縛 + 名前付き函数の束縛
(defun fact-slow (n) ; 非末尾再帰 (if (zerop n) 1 (* n (fact-slow (- n 1)))))
(defun fact/tco (n) ; 末尾再帰 (fact-aux n 1))(defun fact-aux (n r) (if (zerop n) r (fact-aux (- n 1) (* r n))))
(defun fact (n) ; 名前付き let (let iter (n r) (if (zerop n) r (iter (- n 1) (* r n))))
末尾再帰の最適化
「 Emacs Lisp に末尾再帰の最適化が欲しい」という話は少なくとも 2004年に GNU のメーリングリストに流れてる . (Oliver Scholz, “tail recursion hack in Emacs Lisp?”)
2013 年までは nlet.el というのもあった . ( 今は消えてる )
「末尾再帰最適化は理論上は実装可能 (unwind しちゃまずいケースを除いて ) です。が、末尾再帰最適化が行なわれることを想定したコードを古いEmacs で実行すると悲惨なことになるので、結局のところ永遠に実装されないと思います。」 ( わからん , Emacs Lisp で末尾再帰 , コメント欄 )
個人的な意見 : 古い Emacs のことを考えて読めないプログラムを書きたくないし読みたくない .
Scholz 版 と nlet.el
コンパイル時にマクロで再帰呼び出しを while ループに書き換える
だがバグがあり使い物にならない
引数の評価の順序に依存 (nlet.el)
制限も強い
末尾再帰の形でなかったり progn を混ぜるとバグる
本当に扱いたいのは末尾再帰の場合だが書いてるときは色々試したい
funcall とか mapcar できない
実は変換後のループが函数呼び出ししていて効率が悪い
erfi:let
インターフェースは SRFI 5 (named let)
R5RS から末尾文脈の概念
実装は Scholz 版を参考に
nlet.el も bug は取れたけど生成されるコードの質が良くなかった
例
(defun fact (n) (erfi:let iter ((n 5) (r 1)) (if (zerop n) r (iter (1- n) (* r n)))))
(defun fact (n) (let ((--erfi-continue-- t) (--erfi-result-- nil) (G4201 n) (G4202 1) (n nil) (r nil)) (while --erfi-continue-- (setq n G4201) (setq r G4202) (catch '--erfi-repeat-- (setq --erfi-result-- (if (zerop n) r (progn (setq G4201 (1- n)) (setq G4202 (* r n)) (throw '--erfi-repeat-- nil)))) (setq --erfi-continue-- nil))) --erfi-result--)
展開
変数適用順序に非依存
入れ子の例 : リストの等価性
(defun erfi:list= (elt= &rest xss) (if (let1 len (length (car xss)) (not (erfi:every1 (lambda (xs) (eq len (length xs))) (cdr xss)))) nil (erfi:let outer-iter ((xss xss)) (if (null (cdr xss)) t (erfi:let inner-iter ((xs (car xss)) (ys (cadr xss))) (if (null xs) (outer-iter (cdr xss)) (and (funcall elt= (car xs) (car ys)) (inner-iter (cdr xs) (cdr ys)))))))))
流石に展開したのは載せられないので家に帰って macroexpand してね
生で while ループで書きたくないものの一つ
Haskell の話
Haskell って良い言語ですよね
((圏論好きなので )Haskell の方に行ってみたい )
(( でも ) (括弧 ( ない ( と )) 不安 ))
Lisp はどんどん Haskell とかの良い点を取り込むべき
Gauche も $ をマクロとして取り込んだりしてる
括弧があるからこそ簡単に可能な Lisp の強みのマクロ !!
( とか言ったら Haskeller からマサカリ飛んできそう )
レコード構文data Person = Person { firstName :: String , lastName :: String , age :: Int , phoneNumber :: String } deriving (Show)data Book = Book { author :: Person , title :: String , price :: Int } derivin (Show)
let author = Person "Saunders" "MacLane" 30 "unknown" let book = Book author "Categories for the Working Mathematician" 8500 book { author : author book { lastName : "Mac Lane" , age : 95 , phoneNumber : "unknown" } , price : 3000 }コピーして返す (非破壊的 )但しいくつかは新しい値に
再帰的に使える !
Emacs Lisp にもレコード構文を
XMonad-oid on Emacs に使いたい ( 使ってる )
evm-util.el (2013 年 )
hairs.el (2014 年 )
ewm-util.el
cl.el の構造体を再帰的に非破壊的にコピー
構造体の ( 型 )情報を動的に取得している
cl.el は getter は作るけど setter は作らない
setf ( 汎変数の仕組み ) を使わざるを得ない
なので函数として実装 (遅い )
hairs.el(Haskell influenced record syntax)
動的型付け言語の Lisp だけどこういう部分では静的にやった方が良い
型情報を与えてやれば静的にできる
マクロとして DSL を実装
コンパイルで効率の良いコードを生成
でもいちいち指定するのは面倒
型推論 ( というほどのものではないけど )
alist, plist, hash-table, array, struct, eieio (CLOS) に対応
(hairs-copy '((:hoge . hoge) (:fuga . fuga) (:foo . foo) (:bar . (:john "Smith" :jane "Smith"))) (with :alist :hoge 'hogehoge :foo => (lambda (x) (list x x)) :bar (with :plist :john "Hi" :jane "Hello")))
'((:hoge . hogehoge) (:fuga . fuga) (:foo . (foo foo)) (:bar . (:john "Hi" :jane "Hello")))
固定値型情報
古い値から新しい値を作る 再帰的
(cl-defstruct hairs:person name age sex)(cl-defstruct hairs:group name description member)(hairs-register-type '((:struct hairs:group) member) '(:alist (:struct hairs:person)))
(with (:struct hairs:group) :member (with :alist b (with (:struct hairs:person) :name "Neo")))
(with (:struct hairs:group) :member (w/ b (w/ :name "Neo")))
Any questions or comments?
Twitter: @keno_ss
GitHub: https://github.com/kenoss
まだコード上げてない ( 近日中に )
コメント , 要望 , pull-req歓迎
ありがとうございました
Twitter: @keno_ss
GitHub: https://github.com/kenoss
まだコード上げてない ( 近日中に )
コメント , 要望 , pull-req歓迎