初めての関数型言語 #emacs #lisp #ruby

概要

1年ぶりにemacs lisp環境を整理していて、文字列のリストの各要素に特定の文字列を追加するということがしたくなりました。

しかし今まではemacs lispは他の人のの設定をコピペしたり変数に代入する程度のことしかしたことがなく、いざ書こうととしたら宣言型ではまったく書けません。 (仕事ではC言語でバリバリの手続き型)

ということで、それを解決するまでの過程ををまとめていきたいと思います。

いきなりemacs lispはハードルが高いので、まずは文法に少しは慣れているRubyで書いてみたいと思います。 1歩1歩進んで行きましょう。

Rubyで手続き的に書いてみる

具体的にやりたいことはこういうことです。

root_dir = "/home/sona_tar/";                             
inc_dirs = ["include", "test/include","abc/include"];
include_dirs = Array.new;                                 
                                                          
inc_dirs.each {|dir|                                      
  include_dirs.push(root_dir << dir);                      
}                                                         
                                                          
p include_dirs;                                           

実行結果

$ ruby loop.rb
["/home/sona_tar/include", "/home/sona_tar/test/include", "/home/sona_tar/abc/include"]

とりあえず手続き型ではできました。 普段はC言語で書いているので配列の要素番号を指定して値を参照しなくて済むだけで高級に感じます。

Rubyでmapを使って書いてみる。

続いてmapを使ってみます。

root_dir = "/home/sona_tar/";                             
inc_dirs = Array["include", "test/include","abc/include"];
include_dirs = inc_dirs.map {|dir| root_dir << dir }       
                                                          
p include_dirs;

実行結果

$ ruby loop.rb
["/home/sona_tar/include", "/home/sona_tar/test/include", "/home/sona_tar/abc/include"]

すっきりしました。いちいちリストに追加するという処理を記述する必要がありません。

Rubyでlistに対する処理だけで記述してみる。

冗長ですがこんな書き方もできますね。

root_dir = ["/home/sona_tar/"];                          
inc_dirs = Array["include", "test/include","abc/include"];
include_dirs = root_dir.product(inc_dirs).map {|dir| dir.join }   
                                                          
p include_dirs;

実行結果

$ ruby loop.rb
["/home/sona_tar/include", "/home/sona_tar/test/include", "/home/sona_tar/abc/include"]

emacs lispで手続き的に書いてみる

これを書くのが一番苦労しました。 その割りに空のリストの宣言だったりループしてたりとかっこ悪い。

(setq root_dir "/home/sona_tar/")                                                     
(setq inc_dirs '("include" "test/include" "abc/include"))                             
(setq include_dirs)                                                                   
(dolist (dir inc_dirs)  (setq include_dirs (cons (concat root_dir dir) include_dirs)))
(message "%s" include_dirs)                                                                                  

実行結果

M-x eval-buffer
(/home/sona_tar/include /home/sona_tar/test/include /home/sona_tar/abc/include)

emacs lispでcollectを使って書いてみる。

続いてcl-loopとcollectを使ってみます。

(setq root_dir "/home/sona_tar/")                        
(setq inc_dirs '("include" "test/include" "abc/include"))
(setq include_dirs (cl-loop for dir in inc_dirs collect (concat root_dir dir)))
(message "%s" include_dirs)                                                                            

実行結果

M-x eval-buffer
(/home/sona_tar/include /home/sona_tar/test/include /home/sona_tar/abc/include)

emacs lispでmapcarを使って書いてみる。

続いてmapcarとlambdaを使ってみます。

(setq root_dir "/home/sona_tar/")                        
(setq inc_dirs '("include" "test/include" "abc/include"))
(setq include_dirs (mapcar  '(lambda (dir) (concat root_dir dir)) inc_dirs))
(message "%s" include_dirs)                                                                            

実行結果

M-x eval-buffer
(/home/sona_tar/include /home/sona_tar/test/include /home/sona_tar/abc/include)

まとめ

関数型言語風に書くとloopの記述と状態を保持するリストへの追加処理がなくなり、副作用のないすっきりしたコードになりました。

ただし慣れるまでは頭の使い方が違うので、どのように書けばいいのか考えるのに苦労します。(emacs lispは手続き的に書く方が難しかったですがw)

要訓練です。

もっとスマートな書き方がRubyやemacs lispでありましたら教えてください!

参考

Emacs Lisp勉強会(基礎編) — ありえるえりあ
Emacs Lispのメモ
Loop Facility - Common Lisp Extensions