The Common Lisp Cookbook – 関数

Table of Contents

The Common Lisp Cookbook – 関数

📢 🎓 ⭐ Learn Common Lisp efficiently in videos, by the Cookbook's main contributor. Learn more.

🖊️ Discover a new Common Lisp and Coalton editor for beginners: mine and a new VSCode extension for Common Lisp: OLIVE.

名前付き関数: defun

名前付き関数は defun keyword で作成します。基本形は次のとおりです。

(defun function-name (zero or some arguments)
  "docstring"
  (code of function body))

戻り値は body の最後の expression が返す値です (詳しくは下を参照)。”return xx” statement はありません。

たとえば:

(defun hello-world ()
  ;;               ^^ argument なし
  (print "hello world!"))

呼び出してみます。

(hello-world)
;; "hello world!"  <-- 出力
;; "hello world!"  <-- string が返る

print function は 1 つの argument を standard output に print し、さらにそれを返します。そのため “hello world!” がこの function の戻り値になります。

argument

基本形: required argument

argument は次のように追加します。

(defun hello (name)
  "Say hello to `name'."
  (format t "hello ~a !~&" name))
;; HELLO

(~a は variable を aesthetically に print するために最もよく使われる format directive で、~& は newline を print します)

function を呼び出します。

(hello "me")
;; hello me !  <-- これは `format` により print される
;; NIL         <-- 戻り値: `format t` は string を
;;                 standard output に print し nil を返す

正しい数の argument を指定しないと、明示的な error message とともに interactive debugger に入ります。

(hello)

invalid number of arguments: 0

optional argument: &optional

optional argument は lambda list の &optional keyword の後に宣言します。これらは順序を持ち、続けて現れなければなりません。

この function は:

(defun hello (name &optional age gender) …)

次のように呼び出す必要があります。

(hello "me") ;; required argument への値、
             ;; optional argument は 0 個
(hello "me" "7")  ;; age への値
(hello "me" 7 :h) ;; age と gender への値

名前付き parameter: &key

argument の順序を覚えるのがいつも便利とは限りません。そのため、argument を名前で渡せます。&key argname で宣言し、function call では :argname "value" で設定し、function body では通常の variable として argname を使います。

key argument は default で nil です。

(defun hello (name &key happy)
  "If `happy' is `t', print a smiley"
  (format t "hello ~a " name)
  (when happy
    (format t ":)~&")))

次の call が可能です。

(hello "me")
(hello "me" :happy t)
(hello "me" :happy nil) ;; 不要、(hello "me") と同等

一方、これは valid ではありません: (hello "me" :happy):

odd number of &KEY arguments

複数の key parameter を持つ function declaration の類似例です。

(defun hello (name &key happy lisper cookbook-contributor-p) …)

これは 0 個以上の key parameter を任意の順序で指定して呼び出せます。

(hello "me" :lisper t)
(hello "me" :lisper t :happy t)
(hello "me" :cookbook-contributor-p t :happy t)

最後に、すぐ気づくことですが、key は programmatically に選べます (variable にできます)。

(let ((key :happy)
      (val t))
  (hello "me" key val))
;; hello me :)
;; NIL

optional parameter と key parameter の混在

一般には style warning になりますが、可能です。

(defun hello (&optional name &key happy)
  (format t "hello ~a " name)
  (when happy
    (format t ":)~&")))

SBCL では次のようになります。

; in: DEFUN HELLO
;     (SB-INT:NAMED-LAMBDA HELLO
;         (&OPTIONAL NAME &KEY HAPPY)
;       (BLOCK HELLO (FORMAT T "hello ~a " NAME) (WHEN HAPPY (FORMAT T ":)~&"))))
;
; caught STYLE-WARNING:
;   &OPTIONAL and &KEY found in the same lambda list: (&OPTIONAL (NAME "John") &KEY
;                                                      HAPPY)
;
; compilation unit finished
;   caught 1 STYLE-WARNING condition

呼び出すことはできます。

(hello "me" :happy t)
;; hello me :)
;; NIL

key parameter の default value

lambda list では、次の (happy t) のような pair を使って optional argument や key argument に default value を与えます。

(defun hello (name &key (happy t))

これで happy は default で true です。

key parameter は指定されたか?

この tip は必要なら今は飛ばしてかまいませんが、便利になることがあるので後で戻ってきてください。

default の key parameter は default で nil だと見ました ((defun hello (name &key happy) …))。しかし、「値が default で NIL である」ことと「user が NIL にしたい」ことはどう区別できるでしょうか。

default value を設定するには 2 要素の list を使うことを見ました。

&key (happy t)

この疑問に答えるには、次のような triple を使います。

&key (happy t happy-p)

ここで happy-p は key が渡されたかどうかを知るための predicate variable として働きます (-p を使うのは convention にすぎないので、好きな名前を付けられます)。渡されていれば T になります。

これで、:happy が明示的に NIL に設定された場合は sad face を print します。default では print しません。

(defun hello (name &key (happy nil happy-p))
  (format t "Key supplied? ~a~&" happy-p)
  (format t "hello ~a " name)
  (when happy-p
    (if happy
      (format t ":)")
      (format t ":("))))

可変個数の argument: &rest

function に可変個数の argument を受け取らせたいことがあります。&rest <variable> を使います。この <variable> は list になります。

(defun mean (x &rest numbers)
    (/ (apply #'+ x numbers)
       (1+ (length numbers))))
(mean 1)
(mean 1 2)  ;; => 3/2 (そう、ratio として print される)
(mean 1 2 3 4 5) ;;  => 3

key argument を定義し、さらに他も許す: &allow-other-keys

見てみましょう。

(defun hello (name &key happy)
  (format t "hello ~a~&" name))

(hello "me" :lisper t)
;; => Error: unknown keyword argument

一方で:

(defun hello (name &key happy &allow-other-keys)
  (format t "hello ~a~&" name))

(hello "me" :lisper t)
;; hello me

argument を受け渡ししたり、function をより高い level で操作したりするとき、&allow-other-keys が必要になることがあります。

実例です。常に :if-exists :supersede を使って file を open する function を定義しますが、他の key はそのまま open function に渡します。

(defun open-supersede (f &rest other-keys &key &allow-other-keys)
  (apply #'open f :if-exists :supersede other-keys))

:if-exists argument が重複した場合、最初に渡したものが優先されます。

戻り値

function の戻り値は、body で最後に実行された form が返す値です。

non-local exit の方法 (return-from <function name> <value>) もありますが、通常は必要ありません。

Common Lisp には multiple return value という概念もあります。

multiple return value

multiple value を返すことは、結果の list を返すこととは違います

簡単な例

1 つの値を返す function を定義します。

(defun foo ()
  :a)

次に、この function call の結果を variable に設定します。

(defparameter *var* (foo))

*var* は今 :a です。これはごく普通の behaviour です。

今度は values を使って、この function が multiple values を返すようにします。

(foo ()
  (values :a :b :c))

そして再びその結果を *var* に設定します。

(setf *var* (foo))

*var* の値は何でしょうか。:a のままです。残りの value を capture するようには要求していないので、:b:c は捨てられました。

これは実際とても便利です。function が以前より多くの multiple value を返すように変更しても、call site を変更したり refactor したりする必要がありません。

multiple value を返す: values

function values は multiple value を返すために使います。

(values 'a 'b)
;; => A
;; => B

argument なしで values を call すると、値をまったく返しません。

これは nil を返すこととは違います。

下で説明する function を使って multiple value を capture しない限り、他の function から見えて使われるのは最初の value だけです。

(+ (values 1 2 3) (values 10 20 30))
;; => 11

values は list を作りません。

multiple value がある理由。CL built-in を見る

ほとんどの Common Lisp form は 1 つの value を返しますが、function が複数の value (または 0 個) を返せると便利なことがあります。

たとえば round は、丸めた結果と、丸めのために取り除かれた量という 2 つの value を返します。

(round 10.33333333)
;; => 10
;; => 0.33333302

ほとんどの場合は丸めた value だけが必要ですが、何らかの理由で remainder を知りたいなら capture できます。function が計算したすべての value がほとんどの場合に使われると予想するなら、結果を list、CLOS instance、etc., にまとめて返す方がよいでしょう。multiple value は、最初の value が最も頻繁に必要で、後の value はあまり使われない場合にだけ使います。

同様に、hash-table の内容を取得すると 2 つの value が返ります。結果と、その key が見つかったかどうかを示す boolean です。下を参照してください。

multiple value の capture: multiple-value-bindnth-valuemultiple-value-list など

multiple value を capture する最も一般的な方法は multiple-value-bind です。

(multiple-value-bind (c d) (values 1 2)
  (list c d))
;; => (1 2)

;; 次のように indent されることも多い:
(multiple-value-bind (c d)
    (values 1 2)
  (list c d))

これは let binding のように動作します。value cdmultiple-value-bind の scope 内に存在します。

返される value の数と、bind する variable の数は一致していなくてもかまいません。value が多すぎる場合、余分なものは捨てられます。bind する variable が多すぎる場合、余分な variable は nil に設定されます。

(multiple-value-bind (a b) (values 1 2 3 4)
  (list a b))
;; =>(1 2)
(multiple-value-bind (a b) (values 1)
  (list a b))
;; => (1 NIL)

function valuessetf 可能で、これを value の capture に使うこともできます。

(let (c d)
  (setf (values c d) (values 1 2))
  (list c d))
;; => (1 2)

同等の multiple-value-setq も使えます。

(let (c d)
  (multiple-value-setq (c d) (values 3 4))
  (list c d))
;; => (3 4)

また、multiple-value-call で multiple value を直接 function call に送り込めます。

(multiple-value-call #'list (values 1 2 3))
;; => (1 2 3)

function multiple-value-list は上の code と同等です。

(multiple-value-list (values 1 2 3))
;; => (1 2 3)

逆に、values-list で list を multiple return value に変換できます。

(values-list '(1 2 3))
;; => 1
;; => 2
;; => 3

nth-value で特定の value を選択できます。

(nth-value 0 (values :a :b :c))  ;; => :A
(nth-value 2 (values :a :b :c))  ;; => :C
(nth-value 9 (values :a :b :c))  ;; => NIL

ここでも、values は list とは違うことを改めて強調しておきます。

(nth-value 0 (list :a :b :c)) ;; => (:A :B :C)
;; => list はそれ自体で 1 つの data structure

(nth-value 1 (list :a :b :c)) ;; => NIL
;; => capture する 2 番目の value はない

成功または失敗を報告するために multiple value を使う

multiple value の典型的な用途は、nil が見つかった場合と lookup failure を区別することです。たとえば gethash は 2 つの value を返します。1 つ目は lookup の結果です。何も見つからなければ nil かもしれませんが、hashtable に実際に保存されている nil かもしれません。2 つ目の戻り値は lookup が成功したかどうかを示す flag です。

(defvar *hash* (make-hash-table))
(setf (gethash 'a *hash*) 12)
(setf (gethash 'b *hash*) nil)
(gethash 'a *hash*)
;; => 12           <--- 1 番目の戻り値: 結果
;; => T            <--- 2 番目の戻り値: key が見つかった

(gethash 'b *hash*)
;; => NIL
;; => T

(gethash 'c *hash*)
;; => NIL
;; => NIL          <---- この key は見つからなかった

anonymous function: lambda

anonymous function は lambda で作成します。

(lambda (x) (print x))

lambda は funcall または apply で呼び出せます (下を参照)。

quote されていない list の最初の element が lambda expression なら、その lambda が呼び出されます。

((lambda (x) (print x)) "hello")
;; hello

function を programmatically に呼び出す: funcallapply

funcall は argument の数がわかっているときに使います。一方 apply は、たとえば &rest から得た list に対して使えます。

(funcall #'+ 1 2)
(apply #'+ '(1 2))

apply について覚えておくべきことが 1 つあります。非常に大きな list には使えません。function の argument list には長さの制限があります。

この制限は variable call-arguments-limit で確認できます。これは implementation に依存します。SBCL ではかなり大きい (4611686018427387903) ですが、任意の長さの argument に function を適用する別の選択肢があります。それが reduce です。

reduce

reduce は任意の長さの list や vector に function を適用するために使います。function を 2 つの argument で繰り返し呼び出し、argument list を走査します。

たとえば、上のように apply を使う代わりに:

(apply #'min '(22 1 2 3)) ;; 非常に大きな list を想像してください

reduce を使えます。

(reduce #'min '(22 1 2 3))

argument が 1000 elements の長さなら、applymin function を 1000 個の argument で呼び出します。一方 reducemin を毎回 2 個の argument で (ほぼ) 1000 回呼び出します。

reduce は list を走査します。つまり次のようになります。

trace できます。

CL-USER> (trace min)
CL-USER> (reduce #'min '(22 1 2 3))
  0: (MIN 22 1)
  0: MIN returned 1
  0: (MIN 1 2)
  0: MIN returned 1
  0: (MIN 1 3)
  0: MIN returned 1
1

完全な signature は次のとおりです。

(reduce function sequence &key key from-end start end initial-value)

ここで keyfrom-endstartend は他の built-in function にも見られる key argument です (data-structures 章を参照)。:initial-value が与えられた場合、最初の subsequence の前に置かれます。

reduce の詳細は Community Spec を読んでください。

名前で function を参照する: single quote ' か sharpsign-quote #' か?

上の例では #' を使いましたが、single quote も動作し、実際の code でも見かけます。どちらを使うべきでしょうか。

一般には #' を使う方が安全です。lexical scope を尊重するからです。見てみましょう。

(defun foo (x)
  (* x 100))

(flet ((foo (x) (1+ x)))
  (funcall #'foo 1))
;; => 2、期待どおり

;; しかし:

(flet ((foo (x) (1+ x)))
  (funcall 'foo 1))
;; => 100

#' は実際には (function …) の shorthand です。

(function +)
;; #<FUNCTION +>

(flet ((foo (x) (1+ x)))
  (print (function foo))
  (funcall (function foo) 1))
;; #<FUNCTION (FLET FOO) {1001C0ACFB}>
;; 2

function または #' shorthand を使うと local function を参照できます。代わりに symbol を funcall に渡すと、呼び出されるのは常に global environment でその symbol によって名付けられた function です。

さらに、#' は function を value として捕まえます。function が再定義されても、#' でこの function を参照している binding は元の behaviour を実行し続けます。

function を parameter に代入してみます。

(defparameter *foo-caller* #'foo)
(funcall *foo-caller* 1)
;; => 100

ここで foo を再定義しても、*foo-caller* の behaviour は変わりません。

(defun foo (x) (1+ x))
;; WARNING: redefining CL-USER::FOO in DEFUN
;; FOO

(funcall *foo-caller* 1)
;; 100  ;; 2 ではない

caller を single quote の 'foo で bind すると、function は runtime に解決されます。

(defun foo (x) (* x 100))  ;; 元の behavior に戻す
(defparameter *foo-caller-2* 'foo)
;; *FOO-CALLER-2*
(funcall *foo-caller-2* 1)
;; 100

;; 定義を変更する:
(defun foo (x) (1+ x))
;; WARNING: redefining CL-USER::FOO in DEFUN
;; FOO

;; もう一度試す:
(funcall *foo-caller-2* 1)
;; 2

どの behaviour が望ましいかは use case によります。一般には sharpsign-quote を使う方が驚きが少ないです。しかし tight loop を実行していて live-update mechanism (game や live visualisation を考えてください) が欲しい場合は、loop が user の新しい function definition を拾うように single quote を使いたいかもしれません。

higher order function: function を返す function

function を返す function を書くのは十分に簡単です。

(defun adder (n)
  (lambda (x) (+ x n)))
;; ADDER

ここでは、function typeobject を返す function adder を定義しました。

結果として得られる function を呼び出すには、funcall または apply を使う必要があります。

(adder 5)
;; #<CLOSURE (LAMBDA (X) :IN ADDER) {100994ACDB}>
(funcall (adder 5) 3)
;; 8

すぐに呼び出そうとすると illegal function call になります。

((adder 3) 5)
In: (ADDER 3) 5
    ((ADDER 3) 5)
Error: Illegal function call.

実際、CL では function と variable に異なる namespace があります。つまり、評価される form の中での位置によって、同じ name が別のものを指すことがあります。

;; symbol foo は何にも bound されていない:
CL-USER> (boundp 'foo)
NIL
CL-USER> (fboundp 'foo)
NIL
;; variable を作る:
CL-USER> (defparameter foo 42)
FOO
CL-USER> foo
42
;; これで foo は "bound" されている:
CL-USER> (boundp 'foo)
T
;; しかし function としてはまだ違う:
CL-USER> (fboundp 'foo)
NIL
;; では function を定義する:
CL-USER> (defun foo (x) (* x x))
FOO
;; これで symbol foo は function としても bound された:
CL-USER> (fboundp 'foo)
T
;; function を取得する:
CL-USER> (function foo)
#<FUNCTION FOO>
;; 短縮表記:
CL-USER> #'foo
#<FUNCTION FOO>
;; 呼び出す:
(funcall (function adder) 5)
#<CLOSURE (LAMBDA (X) :IN ADDER) {100991761B}>
;; そして lambda を呼び出す:
(funcall (funcall (function adder) 5) 3)
8

少し単純化すると、CL の各 symbol には情報を保存する「cell」が (少なくとも) 2 つあると考えられます。1 つの cell は value cell と呼ばれることがあり、この symbol に bound された value を保持できます。boundp を使うと、symbol が (global environment で) value に bound されているかを test できます。symbol の value cell には symbol-value で access できます。

もう 1 つの cell は function cell と呼ばれることがあり、symbol の (global な) function binding の定義を保持できます。この場合、symbol はその定義に fbound されていると言います。fboundp を使うと、symbol が fbound されているかを test できます。symbol の function cell には (global environment で) symbol-function で access できます。

さて、symbol が評価されると、variable として扱われ、その value cell が返されます (単に foo)。compound form、つまり cons が評価され、その car が symbol なら、この symbol の function cell が使われます ((foo 3) のように)。

Common Lisp では Scheme と異なり、評価される compound form の car を任意の form にすることはできません。symbol でない場合、それは (lambda lambda-list form*) の形をした lambda expression でなければなりません。

これが上で得た error message の理由です。(adder 3) は symbol でも lambda expression でもありません。

compound form の car で symbol *my-fun* を使えるようにしたいなら、その function cell に明示的に何かを保存する必要があります (通常これは macro defun が行ってくれます)。

;;; 上からの続き
CL-USER> (fboundp '*my-fun*)
NIL
CL-USER> (setf (symbol-function '*my-fun*) (adder 3))
#<CLOSURE (LAMBDA (X) :IN ADDER) {10099A5EFB}>
CL-USER> (fboundp '*my-fun*)
T
CL-USER> (*my-fun* 5)
8

詳しくは form evaluation についての CLHS section を読んでください。

Closures

closure を使うと lexical binding を capture できます。これは、毎回 function に渡したくない state を保存するときに便利です。利便性のためでも、state variable を到達可能な namespace から外しておくためでもあります。

(let ((limit 3)
      (counter -1))
    (defun my-counter ()
      (if (< counter limit)
          (incf counter)
          (setf counter 0))))

(my-counter)
0
(my-counter)
1
(my-counter)
2
(my-counter)
3
(my-counter)
0

または同様に:

(defun repeater (n)
  (let ((counter -1))
     (lambda ()
       (if (< counter n)
         (incf counter)
         (setf counter 0)))))

(defparameter *my-repeater* (repeater 3))
;; *MY-REPEATER*
(funcall *my-repeater*)
0
(funcall *my-repeater*)
1
(funcall *my-repeater*)
2
(funcall *my-repeater*)
3
(funcall *my-repeater*)
0

counter generator に加えて、lexical closure のもう 1 つの一般的な用途は memoization、つまり計算にコストがかかる function の以前の結果を cache することです。下の memoize されていない Fibonacci function は、すぐに計算時間がかなりかかるようになります。

(defun fibonacci (n)
  (if (<= n 1)
      n
      (+ (fibonacci (- n 1)) (fibonacci (- n 2)))))

(time (fibonacci 40))
;; Evaluation took:
;;   2.843 seconds of real time
;;   2.841360 seconds of total run time (2.796188 user, 0.045172 system)
;;   99.93% CPU
;;   0 bytes consed
;; 102334155

以前に計算した結果を hash table に保存すると高速化できます。defvar を使うこともできますが、これは closure に適した use case です。1 つの function だけが使う variable を namespace に追加せずに済むからです。

(let ((memo (make-hash-table)))
  (defun fibonacci (n)
    (let ((value (gethash n memo)))
      (cond ((<= n 1) n)
            (value value)
            (t (setf (gethash n memo)
                     (+ (fibonacci (- n 1)) (fibonacci (- n 2)))))))))

(time (fibonacci 40))
;; Evaluation took:
;;   0.000 seconds of real time
;;   0.000016 seconds of total run time (0.000015 user, 0.000001 system)
;;   100.00% CPU
;;   0 bytes consed
;; 102334155

memoization library はいくつかあり、その一部はこの lexical closure と caching の mechanism を macro で wrap しています。

詳しくは Practical Common Lisp を参照してください。

setf function

function name は、最初の symbol が setf である 2 つの symbol の list にすることもできます。このとき最初の argument は新しい value です。

(defun (setf function-name) (new-value other optional arguments)
  body)

この mechanism は CLOS method でよく使われます。

例に向けて進めましょう。square を表す hash-table を操作するとします。この hash-table に square の width を保存します。

(defparameter *square* (make-hash-table))
(setf (gethash :width *square*) 21)

program の life cycle 中、上で行ったように setf で square の width を変更できます。

square area を計算する function を定義します。dimension と重複するため、これは hash-table には保存しません。

(defun area (square)
  (expt (gethash :width square) 2))

ここで programming 上の必要から、square の *area* を変更し、それを square の dimension に反映できると非常に便利な状況になったとします。program の application interface にとって、次のような setf-function を定義すると扱いやすくなります。

(defun (setf area) (new-area square)
  (let ((width (sqrt new-area)))
    (setf (gethash :width square) width)))

これで次のようにできます。

(setf (area *square*) 100)
;; => 10.0

square を確認すると (describeinspect など)、新しい width が設定されています。

setf-function: optional argument

setf-function が持つ mandatory argument は、新しい value 1 つだけである点に注意してください。2 番目以降の argument は optional です。たとえば Lem editor codebase から:

(defun (setf current-buffer) (buffer)
  "Change the current buffer."
  (check-type buffer buffer)
  (setf *current-buffer* buffer))

この setf-function は global parameter (*current-buffer*) を新しい value に設定します。

2 つより多い argument を持つ setf-function も定義できます。

(defun (setf area) (new-area square x y &key log)
  (list new-area square x y log))

使ってみます。

(setf (area 'square 1 2 :log t) 9)

Currying

concept

関連する概念に currying があります。functional language から来たなら馴染みがあるかもしれません。前の section を読んだ後なら、これはかなり簡単に実装できます。

CL-USER> (defun curry (function &rest args)
           (lambda (&rest more-args)
	           (apply function (append args more-args))))
CURRY
CL-USER> (funcall (curry #'+ 3) 5)
8
CL-USER> (funcall (curry #'+ 3) 6)
9
CL-USER> (setf (symbol-function 'power-of-ten) (curry #'expt 10))
#<Interpreted Function "LAMBDA (FUNCTION &REST ARGS)" {482DB969}>
CL-USER> (power-of-ten 3)
1000

Alexandria library を使う

やり方がわかったので、Quicklisp にある Alexandria library の implementation を使うありがたみもわかるでしょう。

(ql:quickload "alexandria")

(defun adder (foo bar)
  "Add the two arguments."
  (+ foo bar))

(defvar add-one (alexandria:curry #'adder 1) "Add 1 to the argument.")

(funcall add-one 10)  ;; => 11

(setf (symbol-function 'add-one) add-one)
(add-one 10)  ;; => 11

ドキュメント

Page source: ja/functions.md

T
O
C