ITコンサルの日常

ITコンサル会社に勤務する普通のITエンジニアの日常です。

「Ansi Common Lisp」6章読了

関数に関する章。これもLispの核のような気がするので多分重要です。

大域関数

fboundpは関数の存在を確かめる関数。

* (fboundp '+)

T
* (fboundp '&)

NIL
*
ドキュメンテーション

defunで定義した関数の本体前の式(の一つ)がストリングであれば、それは関数のドキュメント(ドキュメンテーションストリング)になる。

コード中にドキュメントを書いてしまおうという、JavaDocのような仕組みがLispにもあるわけですね。
ドキュメントを書いて参照してみる。

* (defun mysum (lst)
    "mysum is copy of '+"
    (apply '+ lst))

MYSUM
* (documentation 'mysum 'function)

"mysum is copy of '+"
* (documentation '+ 'function)

"Returns the sum of its arguments.  With no args, returns 0."
*

関数の名前は分かるけど、どういう動きするんだっけ?
と悩んだらドキュメンテーション参照してみるといいかも。

局所関数

letの関数版らしい。

* (labels ((add10 (x) (+ x 10)))
    (add10 5))
;
15
*

lambdaと違って再帰もできます。

* (labels ((primes (x)
             (if (eql x 1)
               1
               (* x (primes (- x 1))))))
    (primes 5))

120
*

パラメータリスト

レストパラメータ

可変長引数的なやつです。

* (defun my-mean (&rest args)
    (/ (apply '+ args) (length args)))

MY-MEAN
* (my-mean 1 2 3)

2
* (my-mean 4 2 5 8)

19/4
*
オプショナルパラメータ

要は省略可能なパラメータのこと。VBにもそんなのあったような気がします。

* (defun my-sort (lst &optional reverse)
    (if reverse
      (sort lst #'>)
      (sort lst #'<)))

MY-SORT
* (my-sort '(3 5 1 4 2))

(1 2 3 4 5)
* (my-sort '(3 5 1 4 2) t)

(5 4 3 2 1)
*
キーワードパラメータ

引数の順番じゃなくて、:param1 valueみたいに指定できるパラメータのこと。
これもVBにそんな仕様ありましたね。

* (defun my-sort (lst &key reverse)
    (if reverse
      (sort lst #'>)
      (sort lst #'<)))

MY-SORT
* (my-sort '(3 5 1 4 2))

(1 2 3 4 5)
* (my-sort '(3 5 1 4 2) :reverse t)

(5 4 3 2 1)
*

ユーティリティ

2.6節でLispの大部分は、ユーザが自分で定義するのと同じようなLisp関数で構成されていることを説明した。こういう特徴がプログラミング言語にあると強力である。あなたは言語にあわせて自分の発想を変更する必要はない。言語をあなたの発想にあうように修正すればよいからである。Common Lispにこんな関数があるとよいと思ったら、それを自分で書くことができるし、その関数は+やeqlと全く同様の意味で言語の一部となるのである。

これまで学んできた範囲では、特殊な構文やキーワードとかはほとんどないので、それがこうした柔軟性と拡張性を生んでいるのが分かります。
まあその代わり、人間には直感的に分かりづらいポーランド記法を強いられているというような、不便な一面もあるような気がしますが。

クロージャ

Java7から取り入れられるとかいうアレですね。
http://journal.mycom.co.jp/column/jsr/054/index.html
を見ると、Haskellの無名関数そのものって感じもしますが。

関数が外部で定義された変数を参照するとき、その変数をフリー変数と呼ぶ。フリーレキシカル変数を参照する関数はクロージャ(closure)と呼ばれる。
クロージャは関数と環境を一緒にしたものである。

なんとなく、関数と変数を一体化したものかなあって思ってたけど、関数と環境ときましたか。うーん、なんかいまいち。
とりあえず載ってるサンプルを動かしてみる。

* (defun add-to-list (num lst)
    (mapcar #'(lambda (x)
                (+ x num))
            lst))

ADD-TO-LIST
* (add-to-list 5 '(1 2 3))

(6 7 8)
*

lambdaの中で使っているnumが、フリー変数(フリーレキシカル変数)で、lambda自体がクロージャってことになるんでしょうね。
Haskellのwhere節にも近い概念のような気がします。


ちなみに注記にクロージャの有用性の多くの例については、SICPを参照とあります。
そこに行き着くわけですか。

関数ビルダ

関数を返す関数のことを指すらしい。
Haskellでいうところの関数合成や、関数のカリー化を実現する方法が載ってます。

動的スコープ

Wikipediaによると、静的スコープは、

静的スコープ(せいてき- static scope)とは、プログラミング言語におけるスコープの一種。構文構造のみから決定できるため構文スコープ (こうぶん- lexical scope)ともいう。

ブロックなどの構造を持つプログラミング言語では、あるブロックの内側で定義された変数はそのブロックの外側から操作することができないのが普通である。

動的スコープは、

動的スコープ、ダイナミックスコープ(dynamic scope)とは、プログラミング言語におけるスコープの一種。

動的スコープは、静的スコープ(構文構造のみから決定できるスコープ)に加え、実行時の親子関係の子側(呼び出された側)から親側(呼び出し側)のスコープを参照できるスコープである。

とあります。なんか非常に分かりやすい説明ですね。

静的スコープ - Wikipedia
動的スコープ - Wikipedia

関数(メソッド)呼び出しスタック内でのグローバル変数みたいなイメージですかね。
動的スコープの説明には、

動的スコープは強力な反面、ミスを招きやすいため使用に注意が必要である。

とあるように、なんかあんまり使わない方が良いような気もします。

コンパイル

compile関数で関数をコンパイルし、compiled-function-pでコンパイルされているかどうか確かめる。

* (compiled-function-p #'+)

T
* (defun twice (x)
    (* x 2))

TWICE
* (compiled-function-p #'twice)

T
* (compile 'twice)
; Compiling LAMBDA (X):
; Compiling Top-Level Form:

TWICE
NIL
NIL
* (compiled-function-p #'twice)

T
*

CMUCLインタプリタで実行したところ、defunが終わった時点でもうコンパイル済みになってます。意味ないですが、改めてコンパイルすることも出来ました。

再帰の利用

再帰は以下の理由により、使うべきだという主張らしい。

最後のやつなんかはまさにLispって感じですが、再帰関数を書いて動くとなんかうれしいってのは確かにありますね。
ただ、最初の副作用を出来るだけ使わないようにして、バグをなくすっていうのが重要な気がします。