スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

F#入門初級編第6回(関数(ラムダ式))

今回のお題は「関数(ラムダ式)」です。
 
基本編で関数を導入したとき、最初に次のような形を紹介しました。
 
(fun (x :int) -> x + 1) //#1
 
F#Interactiveで実行すると、
val it : int -> int = <fun:clo@13>
となります。これは名前が付いていない状態の関数(値)で、匿名関数ともラムダ式とも呼ばれます。
値に対して適用するには、これを、適用される値の前において、次のようにするのでした。
 
> (fun (x :int) -> x + 1) 3;;
val it : int = 4
 
今後の説明のために、これを比喩的に表してみたいとおもいます。
まずマトリョーシカをイメージしてください。(分からない人はhttp://ja.wikipedia.org/wiki/%E3%83%9E%E3%83%88%E3%83%AA%E3%83%A7%E3%83%BC%E3%82%B7%E3%82%AB%E4%BA%BA%E5%BD%A2を参照してください。)
ただここで、考えるマトリョーシカには開けるのに、鍵が必要であるとしてください。鍵穴には、合うタイプのカギの種類が例えばint型とかfloat型とか書いてあるとします。
 
#1の例の(fun (x :int) -> x + 1)は、この比喩でいうと、「一重のマトリョーシカで、鍵穴xはint型と書いてあって、中身にx+1が入っている」というものです。
さて、鍵を開けて中身を取り出すには、鍵穴xに書いてある型の値を持ってきて、それを押しこみます。すると中身のxはその値になり、中身が出てきます。例えば3を持ってきて、押し込むと、中身のxが3となり、鍵が開いて中身のx(=3)+1すなわち、4が返り値となります。
これは式でいうと(fun (x :int) -> x + 1) 3 と同等となります。
また関数の型でいうと、「鍵穴の種類」->中身の種類 でint->intとなります。
 
さて、次に2変数の関数に移ります。
例えば(fun (x:int) (y:int) -> x + y)ですが、これはF#では、
 
(fun x ->
   (fun y -> x + y))  //#2
 
と同等のものとして扱われます。
これはマトリョーシカの例でいうと、「二重のマトリョーシカで、一番外側のマトリョーシカの鍵穴xにはint型と書いてあり、その中にあるマトリョーシカの鍵穴yにはint型とかいてあり、その中にはx+yがある」という形になります。
関数の型でいうと「鍵穴の種類」->「鍵穴の種類」-> 中身の種類 でint->int->intとなります。
(鍵穴の種類は外側から、並べます。)
 
それでは
 
(fun x ->
   (fun y -> x + y)) 1
   
とするとどうなるでしょうか。
 
この場合、1を持ってきて、押し込むと、このマトリョーシカの内側にあるxがすべて1となり、鍵が開いて中身の(fun y -> 1 + y)が返り値となります。すなわち「内部のマトリョーシカで、xを1にしたもの」が返り値となります。
関数が返り値になるわけです。
 
これが
let sum x y = x + y
let sum1 = sum 1
とかとした、いわゆる関数の部分適用のからくりです。
 
それではひとつ問題です。(fun (x:int) (y:int) (z:int) -> x + y + z)を#2のような形で書いてください。
(答え)
> (fun x ->
   (fun y ->
     (fun z -> x + y + z)));;
val it : int -> int -> int -> int = <fun:clo@3-3>
 
もうひとつ問題です。
(fun x ->
   (fun () ->
     (fun y -> x*x+y*y))) 
においてx=2,y=3のときの値を求めるには、どのような引数を与えたらよいでしょう。
(答え)
一番外側の鍵をx=2で開いて、次はunit型の値()で開き、最後の鍵をy=3で開けばよいので引数は順に2 () 3です。実際にやってみます。
> (fun x ->
   (fun () ->
     (fun y -> x*x+y*y))) 2 () 3;;
val it : int = 13
 
さて最初の
(fun x ->
   (fun y -> x + y)) 
にもどります。
これは普通の関数定義の方法なので、内部でlet式もif文も使えます。

 
> (fun x ->
     let u = x + 1
     (fun y -> y * u));;
val it : int -> (int -> int) = <fun:clo@1>
 
次に3 5に適用してみます。
 
> (fun x ->
     let u = x + 1
     (fun y -> y * u)) 3 5;;
val it : int = 20
 
これは、まず
(fun x ->
     let u = x + 1
     (fun y -> y * u)) 3
でx=3として最初の鍵が開いて、xを全部3で置き換えて、次にuが3+1で4になりますから、uを全部4に置き換えて、(fun y -> y * 4)が残ります。さらにこれを5に適用しますから、20が返るという訳です。
 
このような入れ子のマトリョーシカとマトリョーシカの間に何らかの作用を置いて、マトリョーシカの開き方を、このようなLispライクな括弧たくさんの形から、もっと頭にやさしい形で提供するのが、F#のworkFlowだったりしますので、この入れ子のマトリョーシカ(引数一つの関数)のイメージはよろしければ覚えておいてください。
 
それでは、もうひとつ例を挙げてみます。
> let f g n =
    (g (n + 1)) + 3;;
 
val f : (int -> int) -> int -> int
 
これは(int -> int)型の関数gとint型の数nを引数にとり、intを返す関数です。
 
これを、マトリョーシカ形(入れ子の形)に書いてみてください。
(解答例)
> let f =
    (fun g ->
        (fun n ->
            (g (n+1) + 3)));; 
 
val f : (int -> int) -> int -> int
 
次に、
> (fun g ->
        (fun n ->
            (g (n+1) + 3))) (fun x -> x *x);; 
            
とすると、(fun x -> x *x)という鍵でマトリョーシカが一つ開いて、中のgが (fun x -> x *x)に置き換わったものが出てきます。          
val it : (int -> int) = <fun:it@20>
これは実際的には
(fun n -> (n+1)*(n+1) + 3)と同じものです。
名前を付けて、確かめてみます。
 
> let u =(fun g ->
           (fun n ->
             (g (n+1) + 3)));;  
 
val u : (int -> int) -> int -> int
 
> let u =(fun g ->
           (fun n ->
             (g (n+1) + 3)))(fun x -> x*x);;  
 
val u : (int -> int)
 
> u 1;;
val it : int = 7
 
> u 5;;
val it : int = 39
 
ここで最後にλ計算というのを紹介しておきたいと思います。
これは、ウィキペディアによると「ラムダ計算(lambda calculus)は、理論計算機科学や数理論理学における、関数の定義と実行を抽象化した計算体系である。ラムダ算法とも言う。」という恐ろしげなものらしいのですが、まあよく知らないのでさわりだけ紹介します。
まずλという記号を導入してこれで関数を表すことにします。
例えばF#での
(fun x -> x + 1)は
λx.x+1と表されます。
いわばλのついているのが、マトリョーシカで、その後の文字が鍵穴で、カンマに続いて中身です。
これを例えば3に適用する事をF#では
(fun x -> x + 1) 3ですが、λ計算では
(λx.x+1) 3 で表します。
F#では3で鍵をあけて、中身のxを3で置き換えたものが出てくるのですが、
λ計算でも同様で、λxを取り去って、中身のxを3で置き換えたものを取り出します。
よって(λx.x+1) 3 = 4 です。このような操作を「簡約」というそうです。
 
さて次に二重入れ子のマトリョーシカの例を見てみます。
(fun x ->
   (fun y -> x + y)) 
 
これはλ計算では
λx.λy.x + y と表されます。
(λx.λy.x + y) 1 は簡約により
λy.1 + y になります。(外側のマトリョーシカが開いて、中身のマトリョーシカが出てきた状態です。)
 
なおλ計算ではletに相当するものは出てきません。(たとえばlet u = x + 1などというのは、単にその後のuをx + 1で置き換えれば済むのでとくに必要なものではありません。)
それでは、ifはどうなのか、再帰はどうするのかという話になるのですが、実はこれらはすべてλ式で表現できるそうなのです。この辺の話は、勉強してから、もし理解できたら、またの機会に紹介したいと思います。(その日は来るのか?)
 
それより、気になるのは、このマトリョーシカの例えはうまく伝わるのか?ということなのでした。
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

プロフィール

T GYOUTEN

Author:T GYOUTEN
F#と英単語とフリーソフトと読書に興味があります。
ホームページでフリーソフトも公開しています。どぞ御贔屓に。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
フリーエリア
フリーエリア
blogram投票ボタン
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QRコード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。