Chapter 02

関数評価とラムダ式

TAKIZAWA Yozo
TAKIZAWA Yozo
2021.12.19に更新

関数の表現と式の評価

数学における関数は,入力に対して出力の値を決める規則を指します[1].本書で用いるLISP系言語では,たとえば入力を20および2,関数を*(掛け算)とすると,次の通り,括弧と空白を用いた(関数 入力 入力 ...)の形式をもつとして表現します[2]

fpLISP
(* 20 2)

出力は式と置き換わります.上記の場合,20×2を計算した結果である40と置き換わります.

40

複数の演算子を用いた式10+20×2は次のように表現され,また,置き換わっていきます.ただし,演算子の優先順位は存在しないため,演算の順番は括弧を用いて明示する必要があります.

fpLISP
(+ 10 (* 20 2))

50

このように,入力に対して関数を適用した結果を求めることを評価と呼びます.上記の例では,(+ 10 (* 20 2))を評価した結果が50となります.

注意してほしいのは,関数型プログラミングにおける入出力は,キーボード/モニタ入出力の類と異なり,入力は関数に与える引数,出力は式の評価結果であることです[3].また,次のイメージ図のように,関数規則の中を流れる値は,それぞれの関数入出力によって変化していくものの,その途中で値を保存したり取り出したりする場所は想定されていないことに併せて注意して下さい.

ラムダ式による関数定義

ラムダ式は,引数に名前を付けて新しい関数を定義するための構文です.無名関数とも呼ばれ,関数規則には名前を付けません.本書で用いているプログラミング言語fpLISPでは,lambdaというキーワードを用いて定義します.例として,引数xyを用いた関数規則x+y×2の定義は次のようになります.

fpLISP
(lambda (x y) (+ x (* y 2)))

この関数規則を,たとえば引数x10,引数y20として評価する場合は,前節の式と同じく,ラムダ式を括弧の先頭に置いて表現します.

fpLISP
((lambda (x y) (+ x (* y 2))) 10 20)
50

評価の様子を図として示すと,次のようになります.

値の流れの様子を示すイメージは次のようになるでしょうか.関数『*』への入力のうち,『2』がラムダ式の中にあることに注意して下さい.

先ほど,関数規則には名前を付けない,と述べましたが,実際には,ラムダ式を値として扱うことができるため,ラムダ式を用いてラムダ式に名前を付けることが可能です.次は,ラムダ式(lambda (x y) (+ x (* y 2)))fという名前を付け,上記と同じ値を引数として渡して評価している例です.

fpLISP
((lambda (f) (f 10 20)) (lambda (x y) (+ x (* y 2))))

50

次は,上記のラムダ式のイメージ図です.ラムダ式が値として別のラムダ式の中にコピーされ,評価に用いられていると捉えるとちょうど良いでしょうか.

ラムダ式を値として扱うことができるということは,関数出力としてラムダ式を返すことも可能です.次は,値として入力した(lambda (x y) (+ x (* y 2)))に引数xとして値10のみを渡し,もうひとつの値を引数として受け取る新しいラムダ式を定義して返すラムダ式です.

fpLISP
((lambda (f) (lambda (y) (f 10 y))) (lambda (x y) (+ x (* y 2))))

ラムダ式のままでは計算結果が得られませんので,あらためてふたつ目の値20を渡して評価している例が次の式および評価結果です.

fpLISP
(((lambda (f) (lambda (y) (f 10 y))) (lambda (x y) (+ x (* y 2)))) 20)

50

上記の値の流れを示したイメージが次の図です.ここで,外側のラムダ式の引数yと内側のラムダ式の引数yが別の物であることに注意して下さい.外側のyから内側のyに値20が流れ込み,続けて内側の関数*へと流し込まれることで,値20が留まることなく流れています.

ところで,fpLISPでは,改行と空白は無視されます(空行は評価することを示すものであるため,式の途中に入れることはできません).このことを利用して,次のように,改行と字下げで,ラムダ式の引数と規則本体を複数行に分けることで,少し見やすくすることができます.

fpLISP
(((lambda (f)
    (lambda (y)
      (f 10 y)))
  (lambda (x y)
    (+ x (* y 2))))
 20)

この書き方の場合の評価の様子を次に示します.

述語関数と判断分岐

引数について,ある条件を満たしているかどうかの真偽を返す関数を,述語関数と呼びます.また,述語関数を用いて構成された真偽値を返す式を,条件式と呼びます.fpLISPでは,組み込みの述語関数としてeqltのみを用意しており,それぞれ,数学の=<を意味します.また,真の値はt,偽の値はnilです.
11は等しい?⇒真

fpLISP
(eq 1 1)
t

12は等しい?⇒偽

fpLISP
(eq 1 2)
nil

述語関数による条件式を用いて,判断分岐を表現する構文がif式です.lambdaと同じく括弧内の先頭にifを置き,述語関数を用いた条件式,条件式が真の時に評価される式,条件式が偽の時に評価される式が,空白区切りで続きます.

(if 条件式 条件式が真の時に評価される式 条件式が偽の時に評価される式)

※もし-300より小さければ(* -30 -1)の評価結果を出力し,そうでなければ-30を出力する

fpLISP
(if (lt -30 0) (* -30 -1) -30)

30

※もし300より小さければ(* 30 -1)の評価結果を出力し,そうでなければ30を出力する

fpLISP
(if (lt 30 0) (* 30 -1) 30)

30

if式は(当然ながら)ラムダ式における関数規則に用いることができます.次は,引数xの絶対値を返すラムダ式の例です.
※もしx0より小さければ(* x -1)の評価結果を出力し,そうでなければxを出力する

fpLISP
(lambda (x) (if (lt x 0) (* x -1) x))

これを関数として利用してみた例が次の記述および図です.

fpLISP
((lambda (x) (if (lt x 0) (* x -1) x)) -30)


30
fpLISP
((lambda (x) (if (lt x 0) (* x -1) x)) 30)


30

ラムダ式に入力される引数xの値が,条件式と出力の双方で用いられていること,一方で値が出力に向けて流れている時は,もう一方では全く流れていないことに注意して下さい.

値への名前付け

前々節にて,ラムダ式の引数変数に適用することで,数値やラムダ式に名前を付けることができることを解説しました.このことについて,もう少し詳しく見てみましょう.

一部のプログラミング言語にはletがあり,何度も参照する必要がある計算結果にあらかじめ名前を付けたり,処理内容を一定の範囲内に収めたりするために使われます.LISP系言語では次の構文で表現されます.変数1〜nは(let ...)の中だけで有効な変数であることに注意して下さい[4]

(let ((変数1 値1) (変数2 値2) ... (変数n 値n))
  (変数1~変数nを用いる式))

このletと同じ処理を,ラムダ式の変数への値の適用によって行うことが可能です.

((lambda (変数1 変数2 ... 変数n)
   (変数1~変数nを用いる式))
 値1 値2 ... 値n)

具体的な例は次の通りです.

fpLISP
((lambda (r f)
   (f (* r 2)))
 (+ 10 2) (lambda (x) (+ x 3)))
27

(+ 10 2)の評価結果である12がrという変数名に,(lambda (x) (+ x 3))というラムダ式がfという変数名にそれぞれ適用され[5](f (* r 2))((lambda (x) (+ x 3)) (* 12 2))(+ 24 3)27と置き換わっていきます.

練習問題

次は,ふたつの条件式を引数として指定すると,どちらの条件式も真の時は真の値tを,そうでない場合は偽の値nilを出力するラムダ式の一部です.空欄に適切な記述を解答して下さい.

fpLISP
((lambda (a b) (if        ))
 (eq 0 0) (eq 0 0))
t
fpLISP
((lambda (a b) (if        ))
 (eq 0 1) (eq 0 0))
nil
fpLISP
((lambda (a b) (if        ))
 (eq 0 0) (eq 0 1))
nil
fpLISP
((lambda (a b) (if        ))
 (eq 0 1) (eq 0 1))
nil
脚注
  1. Wikipedia『関数』(2021-11-26参照) ↩︎

  2. LISP系言語ではS式と呼びます. ↩︎

  3. 残念ながら,fpLISPは入出力機器と直接やりとりするための方法は用意されていませんので,入出力は全て,式における関数の引数指定と評価結果となります. ↩︎

  4. 局所変数と呼ばれています. ↩︎

  5. 変数束縛と呼ばれています. ↩︎