第9回 高階関数(6月12日)

今日の課題

■ 引数に関数をとる関数

これまでのプログラミングの練習では、関数の引数には、「12」のように数値や、「"abc"」のように文字列という値を置くことがほとんどであった。ところが、「引数に関数をとる関数」というものを作ることができる。

たとえば、(define f (lambda (a b fnc) (fnc a b))) という定義を行なうと、第一引数と第二引数は値をとるが、第三引数は関数をとる。(fnc a b) は、コレクションの書式なので、a と b を fnc に適用する処理を行う。

たとえば、(f 1 2 +) は、lambda 式を辿ってみると (+ 1 2) を評価することになり、足し算が行なわれるが、(f 1 2 *) は、(* 1 2) となり掛け算が行なわれる。

処理すべき内容を、引数としてとることができるので、変化に富んだプログラムが作成できる。

例題1

リスト lst の要素に対して関数 fnc を適用し、個々の結果を要素とするリストを返す関数 (each lst fnc) を作成せよ。

たとえば、次の動作を期待する:
Interactions 上にて、
> (define bai (lambda (x) (* x 2)))
> (each (list 1 2 3) bai)
(list 2 4 6)
> (define hanbun (lambda (x) (/ x 2)))
> (each (list 1 2 3) hanbun)
(list 1/2 1 3/2)  ← 3/2 は、1と2分の1と、表示されるかもしれない

方針 まず、関数 each は引数として lst と fnc をとるので、ラムダ式で書くと、(lambda (lst fnc) 〈処理のための式〉) となる。さらに、関数名 each とするために define を使うと、(define each (lambda (lst fnc) 〈処理のための式〉)) となる。

〈処理のための式〉は、lst が空のときは空のリストを返すので、(if (null? lst) (list ) 〈空でないときの処理〉) となる。

〈空でないときの処理〉は、全体的にみると、リストを返す処理なので、(cons 〈先頭要素の処理結果〉 〈リストの残りの処理結果〉) となる。

〈先頭要素の処理結果〉は、リスト lst の先頭要素 (car lst) を、関数 fnc の引数に与えて処理した結果であるので、コンビネーションを使って書くと、(fnc (car lst)) となる。

〈リストの残りの処理結果〉は、リスト lst の2番目以降の要素のリスト (cdr lst) に対して関数 fnc を適用したものなので、再帰呼出しで書くと、(each (cdr lst) fnc) となる。

解答 こちら⇒

例題2

ある初期値 s とリスト lst の要素に対して関数 fnc を適用し、その結果とリストの次の要素に対して関数 fnc を適用し、同様にその結果と次の要素に対して関数を適用するという繰返しを行い、最終結果を返す関数 (total s lst fnc) を作成せよ。ここで、fnc の第一引数は、初期値や前処理の結果を受け取る変数であり、第二引数は、リストの要素を受け取る変数であるとする。

たとえば、次の動作を期待する:
Interactions 上にて、
> (define **+ (lambda (s x) (+ s (* x x))))
> (total 0 (list 1 2 3) **+)
14

方針 次の処理が行なわれる。まず、0 と (list 1 2 3)を計算することになり、0 + 1 * 1 を計算し、1 を得る。次に、1 と (list 2 3) を計算することになり、1 + 2 * 2 を計算し、5 を得る。次に、5 と (list 3) を計算することになり、5 + 3 * 3 を計算し、14 を得る。最後に、14 と (list ) を計算することになり、14 を返す。

ところで、5 + 3 * 3 というような計算は、第三引数の関数 fnc が行なうことなので、関数 total では、(fnc 5 3) を実行すれば良い。さらに、5 は total の第一引数 s であり、3 は total の第二引数のリストの先頭要素である。つまり、(fnc s (car lst)) である。これを、total の lambda 式の中で評価するようにプログラムを書けば良い。

解答 こちら⇒

例題3

2つの数値と、四則演算の演算子を収録したリストをそれぞれ引数として受け取り、演算子のリストの要素ごとに、その2つの数値を計算し、その結果をリストに格納して返す関数 calc-oplist を作成せよ。

たとえば、次の動作を期待する:
Interactions 上にて、
> (calc-oplist 12 3 (list + - * /))
(list 15 9 36 4)

解答 こちら⇒

■ 返り値に関数のある関数

これまで作成してきた関数は、返り値が値であった。ところが、返り値が関数である関数を作ることができる。

たとえば、(define g (lambda (x) (if (= x 0) even? odd?))) という関数は、(g 0) を評価すると偶数判定の関数を返し、(g 1) を評価すると奇数判定の関数を返す。

したがって、(g 0) を評価すると #<primitive:even?> という関数の正体が返り値になっている。そして、((g 0) 2) を評価すると、(even? 2) を評価することと同じようになり、#t が返される。



例題4

演算名を引数として受け取り、その計算をする関数を返す関数 func-name を作成せよ。扱う演算名は、'tasu, 'hiku, 'kakeru, 'waru, 'amari であり、それぞれ、+, -, *, /, modulo を返すものとする。

たとえば、次の動作を期待する:
Interactions 上にて、
> (func-name 'tasu)
#<primitive:+>
> ((func-name 'tasu) 1 2 3)
6

解答 こちら⇒

□ 小レポート

おせんべいの面積と値段を計算するプログラムを作ろう。おせんべいは、'maru、'sankaku、'shikaku の3種類である。面積の計算は、'maru は、半径 r とすると 3.14 * r * r とする。'sankaku は、底辺の長さが w、高さが h とすると、0.5 * w * h とする。'shikaku は、底辺の長さが w、高さが h とすると、w * h とする。1つのおせんべいを、1つのリストで表わすことにする。たとえば、(list 'maru 5) は半径 5 の 'maru のおせんべいであり、(list 'sankaku 3 4) は w = 3, h = 4 の 'sankaku のおせんべいである。

問1 おせんべいの種類を入力すると、その面積を計算するための関数を返す関数 menseki を作成せよ。ただし、面積計算に必要な半径、幅、高さは、リストで与えられるものとする。たとえば、次のように、単独で実行できなければならない。
> (menseki 'sankaku)
#<procedure>
> ((menseki 'sankaku) (list 4 5))
10.0

問2 面積当りの価格 u、計算方法 fncs, おせんべいのリスト lst を入力し、値段の合計値を返す関数 senbei を作成せよ。たとえば、次のように実行できなければならない。
> (senbei 10
             menseki
             (list (list 'maru 5)
                   (list 'sankaku 3 4)
                   (list 'shikaku 3 4)))
965.0

小レポートには、次のことを記載すること:

  1. 関数 menseki、関数 senbei のプログラムを印刷し、コメントを手書きすること。特に、引数に関数をとる関数であるか、返り値に関数をとる関数であるのかを説明せよ。
  2. 問1の動作結果を示すこと。3種類のおせんべいの面積の計算が正しいことを示すこと。実行画面のどの部分に注目しなければならないのかを説明すること。
  3. 問2の動作結果を示すこと。少なくとも上記の動作結果が正しく得られることを示せ。実行画面のどの部分に注目しなければならないのかを説明すること。

(c) 2008.6.9 by tokuhisa