2021年2月14日日曜日

無名関数とラムダ式

Excelにラムダ式が導入されたというニュースが少し話題になった.ラムダ式,よく分からない,という人のために,ラムダ式の分かりやすい活用例である無名関数を用いて説明してみよう.この記事で,少しでもラムダ式に親しみを持ってもらえれば本望である.

無名関数

いきなりだが,Wikipediaの「無名関数」からその説明を引用する(太字は筆者による).

プログラミング言語における無名関数(英語: anonymous functionあるいはnameless function)とは,名前付けされずに定義された関数のことである.無名関数を表現するための方法には様々なものがあるが,近年主流となっているのはラムダ式による記法である.

このように,ラムダ式とは無名関数,すなわち「ひとまとまりの処理だが名前が付けられていないもの」であると考えれば分かりやすい.なお,それに対して「ひとまとまりの処理でそれに名前が付けられているもの」は,皆さんおなじみの,関数とかメソッドとかサブルーチンなどと呼ばれるものである.

具体例

たとえば,単純な例だが次の関数定義を考えよう(言語はPythonとする).これは,2乗するだけの極めてシンプルなものなので,説明は不要だろう.

def square(x):
  return x * x

さて,ここで,0から4までの数字が入った配列を考える.

A = list(range(5))

この結果,変数 A には [0, 1, 2, 3, 4] という値が格納される.ここで,全ての要素を2乗したい,つまり,先の square() を配列の各要素に全部適用したいということを考えよう.数学でいえば集合Bを集合Aの各要素が2乗されたものと考えて,集合Aから集合Bに対応させる各要素を2乗する写像 A → を考えるということになる.これをそのままプログラミングとして表現するということだ.

Pythonでは map() が使える.map操作によってAsquareを適用する.結果がmapオブジェクトになるため再びlist化してやる必要があるのが若干面倒ではあるが,上の写像は次の簡単なプログラミング(square()の定義を除けば1行)で表現できる.

B = list(map(square, A))

この処理の結果,変数 B には [0, 1, 4, 9, 16] という値が入る.Pythonインタプリタを用いて確認してみよう.

ラムダ登場

ところで,ここでAだのBだのという変数を使っているが,これって後でそれらを再利用することがなければ本来は不必要だということは,明らかだろう.すなわち,list(map(square, A))は,変数名Aを陽に用いることなしに,次のように書けばよいということである.

list(map(square, list(range(5)))

または

list(map(square, [0, 1, 2, 3, 4]))

でもよい.インタプリタで確かめてみれば,これでも先ほどと同様の結果が得られることを確認できる.では,square() はどうか?これもその関数を別のところで利用することがないのであれば,名前を付けるだけ無駄である.ここでラムダ式の出番である.名前が付いた関数を無名関数で置き換えるのがラムダである.Pythonのラムダ式は

lambda (引数): 処理

と書く.これを使うと,上記の式は次のように書ける.square()の定義も不要なので,ワンライナーになってしまった.スッキリ!

list(map(lambda (x): x*x, [0, 1, 2, 3, 4]))

以上の過程をインタプリタで試してみた結果がこちらである.確認されたい.

無名関数のメリット・デメリット

本稿では,map操作を用いてラムダ式のメリットを説明した.その他にも,Cのsort系関数に渡す4つめの引数,関数ポインタで渡している比較関数のような使い方でもその効果を発揮するだろう.本来,それらの処理でも名前の付いた「関数」を渡す必要はないのだ.その処理へのアドレスが渡せれば十分のはず.しかしCにはラムダ式が用意されていないので,仕方がなく関数ポインタでコードへのアクセスを渡しているにすぎない.

冒頭で紹介したWikipediaの記事には,無名関数のメリットとして名前の衝突が起こらないという点が指摘されていた.そりゃそうだろう.名前を付けないわけだから,衝突の起こりようがない.コードをシンプルに書けるだけでなく,このようなメリットもあるということだ.デメリットとしては名前が付いていないので再帰を書くには何らかの工夫が要るとあった.それもそうだが,再帰を書くときは,素直に普通の関数をかけば良いだろう.

mapとリスト内包表記

集合への写像をプログラミングで表現する方法としてmapを紹介したが,Pythonだと「リスト内包表記」という書き方で似たようなことができる.さきほど述べた配列Aの要素を全て2乗した配列Bを求めるコードは,次のように書ける.

B = [ x*x for x in A ]

これは関数square() を用いて次のように書いても同じ結果になる.

B = [ square(x) for x in A ]

ラムダ式の構文こそ陽に出てこないものの,考え方は同じであるということを理解できるだろうか.これらの比較から,「名前は付いていないけれど,一連の処理のまとまりですよ」ということを明示的に示すものがラムダ式であると考えることもできよう.



0 件のコメント:

コメントを投稿