はじめに
- 無名関数(ラムダ関数)に関する記事です
- 言語的に汎用な話です(java、C++など)
- ...が言語によって微妙な差異はあるので適時調べてください
- また汎用的な部分しか話していないのでこれだけ見ても扱えないと思います
- ざっくりとした説明です。一部説明が雑な部分があります。
- 手垢のついた話ではありますが何卒
話すこと
- 無名関数とは
- 無名関数とは何か
- どういう時に使うか
- なぜラムダか
- ラムダ計算について
無名関数とは
よく話されること
- 名前のつけられてない関数
- 一時的に使う関数に使われる
- 何かと忌避されがち
- なんか触ったら計算機科学の話が出てきた
- そもそも関数型を知らないと扱えない?
- しかし一時変数は普通に使う
- 同じように使うならiやらxやらで適当に置けばいい
- おまけに下手に無名関数にすると普通の方法では再帰ができない
- 普通に関数として定義すれば良いのでは?
では無名関数とは何か
無名関数はオブジェクトとしての関数である
普通のオブジェクト(数値、配列など)が変数に代入されるようにオブジェクトとしての無名関数も変数に代入できる
またオブジェクトが変数に代入せずに使えるように無名関数も変数などに代入しなくても使える
つまりどういうことか
こう書かなくても良いように
(全てのオブジェクトを変数に代入する)
#python
start=1
end=100
x=5
y=0
for n in range(start,end):
if n%x==y:
print("fizz")
else:
print(n)
同様にこう書かなくても良い
(関数に名前をつける)
#python
def plus(n):
return n+1
print(list(map(plus, [1,2,3])))
こう書くように
(オブジェクトをそのまま書く)
#python
for n in range(1,100):
if n%5==0:
print("fizz")
else:
print(n)
こう書いても良い
(関数をそのまま書く)
#python
print(list(map(lambda n:n+1, [1,2,3])))
どういう時に使うか
- 名前をつける必要がないとき(先ほどの例)
- オブジェクトとして必要なとき
- 関数に関数を渡すとき
- スコープをつけたいとき
- クロージャとして使うとき
オブジェクトとして必要なとき
関数に関数を渡すとき
先ほどの例
#python
print(list(map(lambda n:n+1, [1,2,3])))
map関数は関数と配列をとり配列の全要素に関数を適用する
他にreduce(縮約)、filter、sortなどが使えるものが多い
こう書くことで操作を抽象化できる
スコープをつけたいとき
オブジェクトは変数に代入できる
->変数はローカルでもグローバルでも良い
->ローカルな関数が作れる
ローカル関数がない場合や明示的に関数のスコープを操作したいときなどに
クロージャ
オブジェクトとして関数を作るときに一緒に変数も入れておける
関数と変数を内包したもの(関数閉包)
関数が関数を返すときクロージャとして変数も内包して返す
->各オブジェクトとして変数をそれぞれで保持(キャプチャ)できる
->変数がどのスコープにあるかによって動きが大きく変わる
(あとクロージャは各言語によって差異が大きいのでうまく吸収してください...)
微妙にわかりづらいので以下の例で
例
x、yそれぞれにクロージャを代入して3回ずつ呼ぶものを考える
以下の例はCommonLisp
letはローカル変数定義、(incf n)はn++と同義
;common-lisp
(let ((x (f))
(y (f)))
(print (funcall x))
(print (funcall x))
(print (funcall x))
(print (funcall y))
(print (funcall y))
(print (funcall y)))
返される関数が使う変数がfの外側で宣言されている場合
それぞれのクロージャは同じ変数を使う
;common-lisp
(let ((n 1))
(defun f ()
(lambda ()
(incf n))))
;2
;3
;4
;5
;6
;7
返される関数が使う変数がfの中、ラムダの外で宣言されている場合
それぞれのクロージャはクロージャにある変数を使う
;common-lisp
(defun f ()
(let ((n 1))
(lambda ()
(incf n))))
;2
;3
;4
;2
;3
;4
返される関数が使う変数がラムダの内側で宣言されている場合
ラムダの中でのみ使える変数を使う
;common-lisp
(defun f ()
(lambda ()
(let ((n 1))
(incf n))))
;2
;2
;2
;2
;2
;2
詳しい話
ラムダ関数が導入されていない(または導入される前の)言語の関数定義は特殊構造
- ラムダ関数のない言語は関数定義に名前を同時に決定しないといけない(場合が多い)
- (言語によっては)常にグローバルで宣言されるためiやjのような使い回しができない
なにが良いか?
オブジェクトとして関数を区別しなくても良い
->関数を受け取れるし関数を返せる
- 足したり(関数合成)
- 返したり(高階関数)
- 適用したり(著名言語のsortは関数を受け取ることができる)
第1級オブジェクト
- 言語が制限を受けずに扱えるオブジェクト
- 例えばCで配列を返すことはできない(配列が第1級オブジェクトではないため)
- 雑に言えば整数と同じように扱うことができるオブジェクトのこと
- 関数型言語は第1級オブジェクトに関数が含まれる
なぜラムダか
ざっくりラムダ計算
- 数式の操作を抽象化したもの
- 式に名前を定義せず使うことができる
- この特徴がラムダ式に応用されている
- α変換、β簡約、η変換で操作する
- 抽象化しただけなので普通の数学関数でも同様の操作は可能
- このラムダ計算の関数を表す記号がラムダ
- これがなぜラムダ記号なのかは議題がそれるので割愛(諸説あるらしい)
まとめ
- 無名関数は関数をオブジェクトとして扱うためのもの
- 名前をつける必要のない時、オブジェクトとして必要なときに使う
- 書いて使って慣れよう