M.Hiroi's Home Page

Common Lisp Programming

お気楽 Common Lisp プログラミング入門

[ PrevPage | Common Lisp | NextPage ]

関数定義

●defun は関数を定義する

Common Lisp で関数を定義する方法を説明します。簡単な例として、数を 2 乗する関数を作ってみます。

リスト : 数を 2 乗する関数

(defun square (x) (* x x))

関数を定義するときは defun (DEfine FUNction) を使います。defun の構文を下図に示します。


defun は数式と比較するとわかりやすいでしょう。


それでは、実際に実行してみます。

* (defun square (x) (* x x))

SQUARE
* (square 4)

16

関数を定義するには名前が必要です。defun の次に関数名をシンボルで定義します。文字列や数値ではいけません。defun で定義された処理内容は、関数名で指定したシンボルに格納されます。

関数名の次にはリストが必要です。これを「ラムダリスト (lambda-list)」といい、関数の引数名をシンボルで定義します。引数を取らない関数は空リスト () を設定します。それから、関数定義で使用する引数のことを「仮引数」、実際に与えられる引数を「実引数」といいます。square の定義で使用した X が仮引数で、(square 4) の 4 が実引数となります。

そして、最後に処理内容を定義します。square の処理内容は (* x x) の 1 つだけですが、defun では複数の S 式を定義することができます。この場合、S 式はリストに並べた順番で評価され、最後に評価された S 式の結果を、その関数の評価結果として返します。

defun は正常に関数を定義できたら、関数名のシンボルを返します。square を実行するには今まで説明したように、リストの先頭に square をセットしその後ろに引数を与えれば、square に定義された処理内容を実行できます。

●レキシカル変数とスペシャル変数

では、ここで変数 X に値が代入されている場合を考えてみましょう。

* (setq x 100)

100
* (square 5)

?

結果はどうなると思いますか。100 の 2 乗で 10000 になるのでしょうか。これはちゃんと 5 の 2 乗が計算されて、結果は 25 になります。そして、square を実行したあとでも X の値は変わりません。

* (square 5)

25
* x

100

関数 square の仮引数 X は、その関数が実行されている間だけ有効です。一般に、このような変数を「局所変数」または「ローカル変数 (local variable)」といいます。Common Lisp では「レキシカル変数 (lexical variable)」といいます。

これに対し、最初 REPL で X に値を代入した場合、その値は一時的なものではなく、その値はずっと残っています。一般に、このような変数を「大域変数」とか「グローバル変数 (global variable)」といいます。

Common Lisp でグローバル変数に相当するのが「スペシャル変数 (special variable)」です。ただし、REPL で値を変数に代入するだけではスペシャル変数にはなりません。マクロ defvar で宣言する必要があります。

defvar symbol

defvar でスペシャル変数を宣言すると、REPL で値を変数に代入するとき、ワーニングが表示されなくなります。なお、Common Lisp のスペシャル変数は、他言語のグローバル変数とは異なる特長があるのですが、ちょっと難しい話になるので、ここでは深く立ち入らないことにします。

Common Lisp はシンボルの値を求めるとき、それがレキシカル変数であればその値を使います。レキシカル変数でなければ、スペシャル変数の値を使います。次の例を見てください。

* (defvar x)

X
* (defvar y)

Y
* (setq x 10)

10
* (setq y 20)

20
* (defun foo (x) (print x) (print y))

* FOO
* (foo 100)

100   <= print の出力
20    <= print の出力
20    <= foo の返り値

print は S 式を出力する関数です。print は改行してから S 式を出力して、最後に空白文字を 1 文字つけます。print は出力した S 式をそのまま返します。

最初にスペシャル変数として X と Y に値を代入します。関数 foo は仮引数として X を使います。foo を実行すると、X はレキシカル変数なので値は実引数の 100 になります。Y はレキシカル変数ではないのでスペシャル変数の値 20 になります。この関係を図に示すと、次のようになります。


このように、スペシャル変数の値はレキシカル変数の値で隠されるわけです。Common Lisp では、変数の種類を見た目で区別できるように、スペシャル変数を '*' (アスタリスク) で囲む慣習があります。ここでは説明のために、スペシャル変数とレキシカル変数に同じシンボル X を使いましたが、スペシャル変数を定義する場合は慣習に従って *X* としたほうがよいでしょう。

●let はレキシカル変数を定義する

関数の仮引数はレキシカル変数として扱われますが、このほかに Common Lisp では、レキシカル変数を定義するための特殊形式 let が用意されています。let の構文を見てみましょう。

 (let ((変数1 初期値1)
       (変数2 初期値2)

        ・・・・・・

       (変数M 初期値M)) 
     S式1

     ・・・

     S式M)


    図 : let の構文

let は関数の仮引数のように与えられた名前をレキシカル変数として扱い、あとに続く S 式を順番に評価します。変数は初期値を評価した値に初期化されます。初期値が省略されると変数は NIL に初期化されます。定義されたレキシカル変数は、let の実行が終了するまで有効です。let は最後に評価した S 式の値を評価結果として返します。それでは、簡単な例を示しましょう。

* z

=> エラー "The variable Z is unbound."
* (let ((z 100)) (print z))

100
100
* z

=> エラー "The variable Z is unbound."

最初に変数 Z の値を確認します。Z に値はありませんね。次の let では、まず Z をレキシカル変数として定義して 100 に初期化します。そのあと、(print z) を実行します。最初の 100 は、print が画面へ出力したもので、次の 100 が let の返り値です。let の終了後、変数 Z の値はないままです。let で定義した変数 z は、確かにレキシカル変数として扱われたことがわかります。

●問題

次の関数を定義してください。

  1. 引数を三乗する cubic
  2. 引数を 1/2 にする half
  3. 二つの引数の平均値をとる medium
  4. 二つの引数の二乗の平均値をとる square-medium
  5. 2 点間の距離を求める distance, 座標はリスト (x y) で表すこと












●解答

* (defun cubic (x) (* x x x))

CUBIC
* (cubic 3)

27
* (cubic 9)

729
* (defun half (x) (/ x 2))

HALF
* (half 10)

5
* (half 5)

5/2
* (half 2.5)

1.25
* (defun medium (x y) (half (+ x y)))

MEDIUM
* (medium 2 4)

3
* (medium 5 6)

11/2
* (medium 5.0 6.0)

5.5
* (defun square-medium (x y) (half (+ (* x x) (* y y))))

SQUARE-MEDIUM
* (square-medium 2 3)

13/2
* (square-medium 2.0 3.0)

6.5
リスト : 2 点間の距離

(defun distance (p q)
  (let ((dx (- (first p) (first q)))
        (dy (- (second p) (second q))))
    (sqrt (+ (* dx dx) (* dy dy)))))
* (distance '(1 1) '(0 0))

1.4142135
* (distance '(10d0 10d0) '(0 0))

14.142135623730951d0

述語

述語 (predicate) は「真 (true)」か「偽 (false)」を返す関数です。Common Lisp の場合、偽は NIL で表し、それ以外の値を真と判断します。述語の場合、条件を満たす場合はシンボル T を返します。シンボル T は真を表す代表選手なのです。

●等値の術語

関数 equal は 2 つの引数が同じ値か調べます。簡単な例を示します。

(equal (+ 1 2 3) 6)       => T
(equal (+ 2 3 4) 7)       => NIL
(equal 4 4.0)             => NIL ; 型が違うとダメ
(equal 'a 'a)             => T
(equal 'a 'b)             => NIL
(equal '(a b c) '(a b c)) => T
(equal '(a b c) '(a b d)) => NIL

関数 eq は、2 つの引数がまったく同じであるか調べます。これは、コンピュータのメモリ番地 (address, アドレス) を調べます。同じシンボルであれば eq は T を返します。しかし、同じ値の数値でも eq は T を返さない場合があります。

(eq 'a 'a)       => T
(eq 1d100 1d100) => NIL

Lisp は数値アトムを生成する場合、同じ数値でも違うメモリ番地に実体を割り当てる場合があるからです。これはリストの場合も同様です。次の例を見てください。

(eq '(a b c) '(a b c)) => NIL

あと、Common Lisp には eql と equalp が用意されています。eq, eql, equal, equalp の違いは次のようになります。

等しいと判定される範囲が eq < eql < equal < equalp と広がっていきます。

●数に関する述語

関数 zerop number は引数 number がゼロであれば T を、そうでなければ NIL を返します。

(zerop 0) => T
(zerop 1) => NIL
(zerop 0.0) => T
(zerop 0.1) => NIL

関数 plusp number は引数 number が正であれば T を返します。逆に、関数 minusp number は number が負であれば T を返します。

(plusp 1)     => T
(plusp -1.1)  => NIL
(minusp 1)    => NIL
(minusp -1.1) => T

関数 oddp integer と evenp integer は引数 integer の奇数と偶数を判定します。

(oddp 1)  => T
(oddp 2)  => NIL
(evenp 1) => NIL
(evenp 2) => T

●数値を比較する述語

次は、数値を比較する述語をまとめて紹介しましょう。

これらの述語は、右側に書いた数式の関係を満たせば T を返し、そうでなければ NIL を返します。引数は 2 つ以上与えてもかまいません。

equal は数値の型が違うと NIL になりましたが、= を使うと引数の型を区別せずに数値が等しいか調べることができます。次の例を見てください。

(= 4 4)   => T
(= 4 4.0) => T
(= 4 4.0 4 4.0) => T
(= 4 4.0 4.1)   => NIL

●データ型を調べる述語

次はデータ型を調べる述語を紹介しましょう。

いずれの述語も引数をひとつ取り、引数が条件を満たせば T を、そうでなければ NIL を返します。関数名が p で終わるのは、述語 (predicate) の頭文字に由来するそうです。atom は例外なので注意してください。

NIL は偽を表すシンボルですが、空リストも表しています。それでは、listp と symbolp で NIL を調べてみるとどうなるのでしょうか。

(listp nil)   => T
(symbolp nil) => T
(atom nil)    => T   ; atom でも t を返す
(consp nil)   => NIL

listp の場合、NIL は真を返すことに注意してください。consp を使うと NIL は偽に判断されます。それから、引数が空リスト (NIL) か調べる述語に null と endp があります。

(null '(a b c)) => NIL
(endp '(a b c)) => NIL
(null ())       => T
(endp ())       => T
(null 'a)       => NIL
(endp 'a)       => エラー

endp の引数はリストでなければいけません。空リストをデータ型とみなすと、null はデータ型を調べる述語と考えることができます。

Common Lisp にはデータ型を表現するために、「型指定子」と呼ばれるシンボルやリストを使います。今まで紹介したデータと型シンボル (型指定子のシンボル) の関係を示します。

list と cons は、関数のほかにも型シンボルとしての役割も持っています。データ型によっては、もっと細かく分類することもできます。型指定子を使ってデータ型を調べる述語に typep があります。簡単な例を示しましょう。

(typep '(a b c) 'list)   => T
(typep "abcdef" 'string) => T
(typep 100 'integer)     => T
(typep 100 'float)       => NIL
(typep 'a  'symbol)      => T

関数 type-of は引数のデータ型を型指定子で返します。次の例を見てください。

(type-of '(a b c)) => CONS
(type-of "abcdef") => (SIMPLE-ARRAY CHARACTER (6))
(type-of 100)      => (INTEGER 0 4611686018427387903)
(type-of 1.23)     => SINGLE-FLOAT

type-of は型指定子 (型シンボルまたは型指定子リスト) を返します。型指定子リストの先頭はシンボルで、残りがそれに付随する型情報になります。Common Lisp は柔軟な型指定が可能ですが、その分だけ仕組みはちょっと複雑になります。ここでは深く立ち入らず、先に進むことにしましょう。

●複数の述語を組み合わせる

複数の述語を組み合わせる場合はマクロ and と or を使います。

(and S式1 S式2 S式3 S式4 ..... )
(or  S式1 S式2 S式3 S式4 ..... )

and は複数の述語を「~かつ~」で結ぶ働きをします。and は与えられた S 式を左から順番に評価します。そして、S 式の評価結果が NIL であれば、残りの S 式を評価せずに NIL を返します。ただし、最後まで S 式が NIL に評価されなかった場合は、いちばん最後の S 式の評価結果を返します。

or は複数の述語を「~または~」で結ぶ働きをします。or は and と違い、S 式の評価結果が NIL 以外の場合に、残りの S 式を評価せずにその評価結果を返します。すべての S 式が NIL に評価された場合は NIL を返します。それでは、簡単な例を示しましょう。

(defun check-number (x) (and (< 10 x) (<= x 20)))
(defun check-number-else (x) (or (>= 10 x) (> x 20)))

最初の例は、引数 X が 10 より大きくて、かつ 20 以下の場合 T を返します。次の例はその逆で、X が 10 以下、または 20 より大きい場合 T を返します。

check-number と check-number-else は逆の関係です。いちいち 2 つの関数を作らなくても結果を逆にすればいいはずです。この場合は not を使います。not は引数が NIL ならば T を返し、それ以外ならば NIL を返す関数です。つまり、真と偽をひっくり返す働きをします。これを否定といいます。ひらたくいえば、「~ではない」という意味になります。次の例を見てください。

(check-number 15)       => T
(not (check-number 15)) => NIL

このように not を使うことで、述語の判定を逆にすることができます。

●問題

次の関数を定義してください。

  1. 引数 n が引数 m の約数か判定する述語 divisorp
  2. 引数 n が 3 または 5 の倍数か判定する述語 three-or-five-multiple
  3. 引数 n が引数 low, high の範囲内にあるか判定する述語 between
  4. リストの要素がただひとつか調べる述語 singlep
  5. リストの要素が二つあるか調べる述語 doublep












●解答

* (defun divisorp (m n) (zerop (mod m n)))

DIVISORP
* (divisorp 15 3)

T
* (divisorp 15 5)

T
* (divisorp 15 7)

NIL
* (defun three-or-five-multiple (n) (or (divisorp n 3) (divisorp n 5)))

THREE-OR-FIVE-MULTIPLE
* (three-or-five-multiple 9)

T
* (three-or-five-multiple 10)

T
* (three-or-five-multiple 11)

NIL
* (defun between (n low high) (<= low n high))

BETWEEN
* (between 5 1 10)

T
* (between 0 1 10)

NIL
* (between 20 1 10)

NIL
* (defun singlep (xs) (and (consp xs) (null (cdr xs))))

SINGLEP
* (singlep ())

NIL
* (singlep '(a))

T
* (singlep '(a b))

NIL
* (defun doublep (xs) (singlep (cdr xs)))

DOUBLEP
* (doublep ())

NIL
* (doublep '(a))

NIL
* (doublep '(a b))

T
* (doublep '(a b c))

NIL

条件分岐

条件分岐は難しい話ではありません。たとえば、「暑いからアイスコーヒーを飲もう」とか、「雨が降りそうだから傘を持っていこう」というように、私たちはそのときの状況によって自分の行動を決めています。プログラミングの世界でも、状況によって次に実行する処理を選択しなければならないことがよくあります。これを「条件分岐」といいます。

●if

簡単な条件分岐は if を使います。if の構文を示しましょう。

(if <条件部> <then節> <else節>)

if は条件部を評価し、その結果が真ならば then 節を評価します。条件部が偽であれば else 節を評価します。else 節は省略することができます。その場合は、条件部で偽と判定されると NIL を返します。これを図に示すと、次のようになります。


(1) が else 節を省略した場合で、「もしも条件を満たすならば then 節を評価する」となります。(2) の場合では、「もしも条件を満たすならば then 節を評価し、そうでなければ else 節を評価する」となります。すなわち、条件によってどちらかの節が評価されることになります。簡単な使用例を示しましょう。

リスト : if の例

(defun even-or-odd (x)
  (if (evenp x)              ; 条件部
      (print "even number")  ; then 節 
    (print "odd number")))   ; else 節 

(defun my-abs (x)
  (if (minusp x) (- x) x))

関数 even-or-odd は引数 X が奇数か偶数かを判断し、その結果を表示します。my-abs は複素数以外の絶対値を求めます。Common Lisp には絶対値を求める関数 abs があるので、名前を my-abs にしました。abs は複素数の絶対値も求めることができます。これらは簡単ですので、すぐにわかると思います。

●when と unless

if の else 節が NIL の場合には、if の代わりに when を使うことができます。逆に、if の then 節が NIL の場合には unless を使うことができます。

(when test S式1 S式2 S式3 ..... )

when は最初に test を評価しその結果が NIL であれば、その後ろの S 式を評価せずに NIL を返します。そうでなければ、S 式を順番に評価し、最後の S 式の評価結果を返します。

unless は when とは逆の働きをします。述語が偽 (NIL) に評価されたときに、引数の S 式を順番に評価します。unless は述語 not を使うと、次のように whenを使って表すことができます。

(unless test S式1 ...) ≡ (when (not test) S式1 ...)

if の場合、then 節や else 節はひとつの S 式しか受け付けませんが、when やunless では、複数の S 式を引数として受け取ることができます。

●cond

複雑な条件分岐には cond を使います。cond はちょっと奇妙な構文をもっています。

 (cond ( 条件部A S式A1 S式A2 ... )
       ( 条件部B S式B1 S式B2 ... )

                ・・・・・

       ( 条件部M S式M1 S式M2 ... )
       ( t       S式T1 S式T2 ... )) 


        図 : cond の構文

cond は複数の節を引数として受け取ります。各節の先頭には条件をチェックする述語があり、条件が成立した場合、残りの S 式を評価します。条件が不成立であれば、次の節に移ります。たとえば、条件部 A が不成立であれば、次の節に移り条件部 B をチェックします。

条件が成立したならば、同じ節にある S 式を順番に評価していきます。そして、いちばん最後に評価された S 式の返り値が cond の返り値となります。したがって、一度節が選択されたら、それ以降の節は評価されません。

もしも、どの条件部も不成立であれば、cond は NIL を返します。ところで、上図ではいちばん最後の節で条件部が T になっていますね。T は真に評価されるので、この節は無条件に実行されることになります。つまり、条件部 A から条件部 M までのすべての条件が不成立でも、最後の節が必ず選択されるのです。cond を図に表すと次のようになります。


簡単な例を示しましょう。

リスト : cond の例

(defun my-signum (x)
  (cond
   ((plusp x) 1)
   ((zerop x) 0)
   (t        -1)))

my-signum は数値 X の符号を 1, 0, -1 の整数で返します。Common Lisp には引数 X が浮動小数点数ならば同じ型で符号を返す関数 signum があるので、名前を my-signum としました。(plusp x) が真ならば X は正なので 1 を返します。(zerop x) が真ならば X はゼロなので 0 を返します。それ以外であれば X は負なので -1 を返します。

実をいうと、signum は abs を使うと簡単に定義できます。

リスト : signum の定義

(defun signum (x)
  (if (zerop x) x (/ x (abs x))))
(signum 10)   => 1
(signum 10.0) => 1.0
(signum 10d0) => 1.0d0
(signum 1/2)  => 1

●case

もう一つ case という条件分岐を紹介しましょう。case は cond とよく似ています。次の図を見てください。

(case キーとなるS式
  ( キーリストA S式A1 S式A2 ... )
  ( キーリストB S式B1 S式B2 ... )

        ・・・・・

  ( キーリストM S式M1 S式M2 ... )
  ( t S式T1 S式T2 ... ))


        図 : case の構文

case は最初にキーとなる S 式を受け取り、そのあと cond と同様に複数の節が続きます。cond には節の先頭に条件部がありましたが、case の場合はキーリストというものがあります。まず、キーとなる S 式を評価します。次に、この評価結果とキーリストに格納された要素を比較します。このとき、キーリスト本体や要素は評価されないことに注意してください。

もし、等しい要素を見つけた場合は、その節の S 式を順番に実行します。等値の判定には述語 eql が用いられます。キーリストの要素がひとつしかない場合は、要素だけ書いてもかまいません。最後の節のキーリストに T または OTHERWISE を指定すると、その最後の節が無条件で実行されます。キーリストに等しい要素が見つからない場合、case は NIL を返します。

それでは簡単な例題として、正方形 (square) と円 (circle) の面積、立方体 (cube) と球 (ball) の表面積を求める関数 area を作りましょう。

リスト : case の例

(defun area (x r)
  (case
   x
   (square (* r r))
   (cube   (* 6 r r))
   (circle (* pi r r))
   (ball   (* 4 pi r r))
   (t 0)))

引数 X に求める図形の種類をシンボルで指定し、引数 r に一辺の長さ (または半径) を指定します。あとは case を使い、X で場合分けするだけです。この場合は cond よりも case を使ったほうがプログラムは簡単になります。なお、pi は円周率を表す定数です。

簡単な実行例を示します。

* pi

3.141592653589793d0
* (area 'square 100)

10000
* (area 'cube 100)

60000
* (area 'circle 100)

31415.926535897932d0
* (area 'ball 100)

125663.70614359173d0
* (area 'rectangle 100)

0

●FizzBuzz 問題その前に

FizzBuzz 問題は 1 から 100 までの値を表示するとき、3 の倍数のときは Fizz を、5 の倍数ときは Buzz を表示するというものです。FizzBuzz 問題の詳細については Fizz Buzz - Wikipedia をお読みください。ここでは問題を解く前に、数値を FIZZ と BUZZ に変換するプログラムを作りましょう。

リスト : FizzBuzz (cond 版)

(defun fizzbuzz (x)
  (cond
   ((zerop (mod x 15)) 'fizzbuzz)
   ((zerop (mod x 3))  'fizz)
   ((zerop (mod x 5))  'buzz)
   (t x)))

最初の節で X が 3 の倍数でかつ 5 の倍数かチェックします。これをそのままプログラムすると次のようになります。

(and (zerop (mod x 3)) (zerop (mod x 5)))

この処理は 15 の倍数をチェックすることと同じなので、関数 fizzbuzz では (zerop (mod x 15)) と書いています。この場合は FIZZBUZZ を返します。シンボルを返すのでクォート ( ' ) を付けることをお忘れなく。そうでなければ、次の節で X が 3 の倍数かチェックし、その次の節で X が 5 の倍数かチェックします。どの条件も該当しない場合は最後の節で X をそのまま返します。

それでは実行してみましょう。

* (fizzbuzz 3)

FIZZ
* (fizzbuzz 5)

BUZZ
* (fizzbuzz 7)

7
* (fizzbuzz 15)

FIZZBUZZ

これを無理やりに case で書き直すと次のようになります。

リスト : FizzBazz (case 版)

(defun fizzbuzz-case (x)
  (case
   (mod x 15)
   (0          'fizzbuzz)
   ((3 6 9 12) 'fizz)
   ((5 10)     'buzz)
   (otherwise  x)))

キーは (mod x 15) の値 (0 - 14) になります。0 の場合、X は 15 の倍数なので FIZZBUZZ を返します。3, 6, 9, 12 の場合、X は 3 の倍数なので FIZZ を返します。5, 10 の場合、X は 5 の倍数なので BUZZ を返します。それ以外の場合は X をそのまま返します。この場合は case よりも cond のほうが直感的でわかりやすいと思います。


Copyright (C) 2020 Makoto Hiroi
All rights reserved.

[ PrevPage | Common Lisp | NextPage ]