Programmer's Note

コード読み書きの備忘録。

fireplace使用メモ

vimClojureの開発が便利になるプラグイン vim-fireplace を導入してみた。

インストール

参照したサイト: http://blog.ieknir.com/blog/beginning-clojure-with-vim/

自分はNeoBundleを使っているので、以下の2つのプラグインを.vimrcに追加しただけ。

 call neobundle#begin(expand('~/.vim/bundle/'))
...
NeoBundle 'tpope/vim-fireplace'
NeoBundle 'tpope/vim-classpath'
...
call neobundle#end()

fireplaceは裏でREPLと通信して、vimでエディットしながら、その場で式の評価などができるので、かなり便利だ。 とりあえずメモ。

vimでコマンド実行するまでの手順

先にREPL環境を立ち上げないとvimでのコマンド実行できない。

(1) leiningen を使ってプロジェクトを作成

$ lein new test

(2) プロジェクト直下に入りlein replを実行する

$ cd test $ lein repl

(3) vimを立ち上げプロジェクト内のファイルを編集する

$ vim src/test/core.clj

関数の評価

vimコマンドラインでREPLの機能を呼び出せる。これだけでかなり便利だ。

下記例は標準関数を使用しているが、自分が作成した関数でも可能だ。 その場合、一旦:wなどでファイル保存してから、関数を使用する必要がある。

vimのコマンド入力で:Evalを使う。

:Eval (println "hello")

結果:

hello
nil

関数のdocを見る

:Doc println

結果:

clojure.core/println
([& more])
  Same as print followed by (newline)

ソースを見る

:Source println

結果:

(defn println
  "Same as print followed by (newline)"
  {:added "1.0"
   :static true}
  [& more]
    (binding [*print-readably* nil]
      (apply prn more)))

キーバインド

cpp

カーソルの行を評価する

例:

(println "hello")

のときに、括弧の中のどこにカーソルを置いてもよく、cppと打てば、helloと実行結果が最下行のバッファに表示される。

cppは複数行にまたがる関数の実行にも使える。→cp)の項目を参照。

cp)

カーソルの下の(と対になる)までの関数を実行する。 例えば

  (loop [x 1]
     (if (> x 11)
       [x]
       (recur (+ x 1))))

があったときに、(loop ...のはじめの(にカーソルを合わせて、cp)と打つと、結果として[12]が最下行のバッファに表示される。

上記は(loop ...)の最初の(と最後の)にカーソルを合わせて、cppと打っても、同じ結果になる。(こちらの方が簡単か)

cpr

カレントnamespaceに対して(require :reload)を実行する

例:

(defn boo []
   (take 6 (iterate inc 0)))
(boo)

としたエディットした直後だと、(boo)にカーソルを置いてcppを打っても、シンボルbooが見つからないと怒られてしまう。 この場合に、 cprを打ってからcppと打つとうまく行く。

K

カーソルの下の関数の(doc ..)の結果を表示する

[d

カーソルの下の関数のソースを表示する

[C-d

カーソルの下の関数のソースに飛ぶ

cqp

REPLのプロンプトが出てくる。

Clojureのプログラミング本とか

先週あたり、ひととおり「プログラミングClojure」を読み終えた。

この本は正直、分かりやすくない。 用語が統一されてない部分があったり、用語の説明がいまいち足りなく、 ネットで検索せんといかんかったり。

サンプルコードも面白い訳でもなく、かといってリファレンスとしても使えないし、 とまあ結構さんざんな言いようだが・・・原著はRich Hickeyさんが ブックリスト に追加しているくらいだから、結構評価されている本なんでしょうね。 (自分には合わんかったというだけかな)

さて、もうちょっとLispのプログラミングを知ろうと思って、 SICP を読み始めた。英語のペーパーバック。 (以前、しょっぱなの数ページだけ読んで止めていた)

一方で、たまたまオンラインでフリーで読める本を見つけた。 Learn to Program the World's Most Bodacious Language with Clojure for the Brave and Trueは、なかなか面白そうだ。

おちゃらけた感じで書かれてるが、こーいうのは好きだな。 (Joel on Softwareもそんな感じだしな。) でもしょっぱなからClojureのクールなところを感じさせる部分を紹介していたり、 なかなかセンスよさげ。これもあとで紙本を買って読みたいところだ。

Clojureメモ: 再帰を使うとか使わないとか

再帰呼び出し。 練習がてら下記のような関数を作ってみる。

(myfunc 5 [])
=> [1 2 3 4 5]

考え方としては、

(myfunc 5, [])
  -> (myfunc 4, [5])
     -> (myfunc 3, [4 5])
        -> (myfunc 2, [3 4 5])
           ...
           -> (myfunc 0, [1 2 3 4 5])
                => 最後に[1 2 3 4 5]を返す

実装すると以下のようになる。

(defn myfunc [n coll]
  (if (<= n 0)
    coll
    (myfunc (dec n) (cons n coll))))
t-recur-func.core=> (myfunc 5 [])
(1 2 3 4 5)

これは、引数にコレクション(ベクター)を渡してしまうが、 コレクションを渡さない以下のやり方も考えられる。

(defn myfunc2 [n]
  (if (<= n 1)
    [n]
    (conj (myfunc2 (dec n)) n)))
t-recur-func.core=> (myfunc2 5)
[1 2 3 4 5]

こっちは、呼び出した関数の返り値を、どんどんくっつけていくパターン。

どちらかというと、個人的にはこちらの方が実装のイメージがしやすかった。

ただし、こちらの方が末尾再帰ではないので、(recur ..)に置き換えることができず最適化できない。 (再帰呼び出しの回数が多いとスタックオーバーフローを起こす)

とはいえ、このパターンでフィボナッチ数列を求めることもできる。

(defn fib [a b n]
    (if (<= n 1)
      [a] 
      (cons a (fib b (+ a b) (dec n)))))
t-recur-func.core=> (fib 0 1 7)
(0 1 1 2 3 5 8)

てな感じだが、 最後の一行がごちゃごちゃしてて、あまり分かりやすくない…。

フィボナッチ数列の求め方は、Clojure的にはやはり、再帰を使わないやり方がエレガント。 以下のような感じ。

(defn fib2 [n]
  (letfn [(_fb [[a b]]
            [b (+ a b)])]
    (take n
        (map first (iterate _fb [0 1])))))
t-recur-func.core=> (fib2 8)
(0 1 1 2 3 5 8 13)

これは「プログラミングClojure」に載っていた解法。 (ポイントを思い出して書いたので、そのままではない)

一旦、

([0 1] [1 1] [1 2] [2 3] [3 5]...)

のシーケンスを求めといて、 それぞれの最初の要素を取り出してしまう。という考え方。

まあ、mapとかiterateとかの仕様を知らないと読めないのだが。 でも、Clojureレバレッジを最大に生かすこの解法には膝を打ったな。

Clojureメモ: マクロの動き

マクロの使い方覚え書き。

まず、「プログラミングClojure」に出ていたノーマルな定義。

unlessというマクロ。

(defmacro unless [expr form]
    (list 'if expr nil form))

実行結果

t-macro.core=> (unless false (println "1"))
1
nil
t-macro.core=> (unless true (println "1"))
nil

Clojureのマクロは、Cのプリプロセッサと同様に、 定義された内容はいったん展開され、その後式として評価される。

(macroexpand-1)を使うと展開した後の式が見える。

t-macro.core=> (macroexpand-1 '(unless true (println "1")))
(if true nil (println "1"))

マクロの中身は(list 'if expr nil form)であるが、 このうちexprformは引数として渡され、評価されずにそのまま展開される。

一方、(list ...)は評価されて、これがマクロの結果として返される。 'ifはリーダマクロ'を使ってifを評価せずにそのまま渡している。 nilは評価されるがnilになる。

ここがポイントのようで、Cのプリプロの単純な文字列置換とはちょっと違う。

マクロを以下のように書き換えてみる。unless-2とする。

(defmacro unless-2 [expr form]
    (if expr nil form))

さっきとの違いは、(list...)を外し、'ifではなくifを使った。

実行してみる。

t-macro.core=> (unless-2 false (println "1"))
1
nil
t-macro.core=> (unless-2 true (println "1"))
nil

一見、結果は一緒。

しかし、(macroexpand-1)すると違いが分かる。

t-macro.core=> (macroexpand '(unless-2 false (print "1")))
(print "1")

マクロとして展開された結果が違う。 今度のはifがマクロ展開時に実行されたので、その結果がマクロ展開の結果として返っている。

上記を見ると、マクロ定義は簡単に言えば 何かしらのClojureプログラムのリストを返す関数を作ること、と思えばよさそうだ。

とすると、さきほど一見、unlessunless-2の実行結果が同じに見えたが、 以下を試してみると、、、

t-macro.core=> (unless (= 1 2) (println "1"))
1
nil

t-macro.core=> (unless-2 (= 1 2) (println "1"))
nil

結果は違ってくる。 macroexpand-1してみる。

t-macro.core=> (macroexpand-1 '(unless (= 1 2) (println "1")))
(if (= 1 2) nil (println "1"))

t-macro.core=> (macroexpand-1 '(unless-2 (= 1 2) (println "1")))
nil

unless-2はなぜnilを返すのだろう?

unless-2はマクロとしては、(if '(= 1 2) nil '(println "1"))を実行する形になる。

t-macro.core=> (if '(= 1 1) "yes" "no")
"yes"

t-macro.core=> (if '(= 1 2) "yes" "no")
"yes"

(if..)関数は、評価なしのformをそのまま渡すと true として見なすようだ。

なお、

t-macro.core=> 'true
true
t-macro.core=> 'fase
fase

で、true, falseをquoteしてもtrue, falseになるようだ。 これ忘れるとハマるな。

vimrcメモ(clojure用plugin設定)

vimrcメモ。 Clojureのための設定も追加。

set nocompatible

set runtimepath^=~/.vim/bundle/neobundle.vim/
call neobundle#begin(expand('~/.vim/bundle/'))

NeoBundleFetch 'Shougo/neobundle.vim'
NeoBundle 'guns/vim-clojure-static'
NeoBundle 'kien/rainbow_parentheses.vim'

call neobundle#end()

" Enable file type detection and do language-dependent indenting.
filetype plugin indent on

NeoBundleCheck

" Switch syntax highlighting on
syntax on

" Show line numbers
set number

" C indent setting
set cindent
set cinoptions+=:0 "Change indent rule of 'case' statement

" Tab width, no expand to space
"set tabstop=4 shiftwidth=4 noexpandtab
set tabstop=4 shiftwidth=4 expandtab

" Highlight search word, increamental hightlight
set hlsearch
set incsearch

autocmd QuickFixCmdPost [^l]* nested cwindow
autocmd QuickFixCmdPost     l* nested lwindow

" show buffer directly
map <C-b> :ls<CR>:buf

" rainbow_parentheses.vimの括弧の色付けを有効化
au VimEnter * RainbowParenthesesToggle
au Syntax * RainbowParenthesesLoadRound
au Syntax * RainbowParenthesesLoadSquare

Clojureメモ: loopとrecur

プログラミングClojureに乗っていた例を写経実行したメモ。

関数を使わずに、再帰的にループするやり方。

  (println (loop [result [] x 5]
         (if (zero? x)
           result
           (recur (conj result x) (dec x)))))

実行結果:

[5 4 3 2 1]

ループ分の構造としては

(loop [※A]
   ...
   ...
   (recur ※B))

になっていて、※Aでは関数でいう引数の定義と、加え初期値の代入をしている。 [result [] x 5]の意味は、result = []x = 5

(recur ※B)では、loopの先頭に戻って、※Bで次のresultxを与えている。

これは、イメージ的には無名関数の再帰呼び出しような感じだなあ。と。

関数でrecurを使う場合は以下のようになる。

(defn count-down [result x]
  (if (zero? x)
    result
    (recur (conj result x) (dec x))))

(println (count-down [] 5))

(loop [result [] x 5](defn count-down [result x]に置き換えただけだ。

初期値は呼び出し側が与えるので、関数の場合は引数の初期値ない。

関数のrecurを使う再帰呼び出し、loopのrecurの記述の一貫性はビューティフルだな。

Clojureメモ: 無名関数

「プログラミングClojure」で出てきたサンプル。

user=> (filter (fn [w] (> (count w) 2)) (str/split "A fine day" #"\W+"))
("fine" "day")

filter関数は、

(filter pred coll)

コレクションcollの各要素に対して、条件をチェックする関数predを適応して、trueが返ってきたものだけを、 戻りのシーケンスに入れて返す。 rubyでいうとArray#selectに相当する。

さて、上記の(fn [w] (> (count w) 2))が無名関数で、リーダマクロ#()を使って、#(> (count %) 2)という形で書ける。

user=> (filter #(> (count %) 2) (str/split "A fine day" #"\W+"))
("fine" "day")

無名関数は任意の変数にバインドできる。 (Clojureのvarは変数とは厳密に違うようだが)

aにバインドして実行してみる。

user=> (def a #(> (count %) 2))
#'user/a
user=> (a "abc")
true
user=> (a "jk")
false

ここら辺の関数の扱いの気軽さはjavascript並みだ。 javascriptで書くと下記のようなものになるかと。

var a = function(w) { return (count(w)>2); }
a("abc);

最後に冒頭の例で、aにバインドされた無名関数を使ってみる。

user=> (filter a (str/split "A fine day" #"\W+"))
("fine" "day")