スポンサーサイト

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

F#入門中級編第一回(Computation Expression(1))

今回のお題は「Computation Expression(1)」です。
 
なお、この節には、F#入門初級編第6回(関数(ラムダ式))で紹介したマトリョーシカの例えが頻繁にでてきますので、できましたら、その部分を読んでから読んでいただきたいと思います。では始めてみます。
 
computation Expressionというのは、大雑把にいうと、自前の構文解釈器です。
コードを、自分で定義した解釈ルールで関数化(厳密にはクラスのインスタンス化)してくれる機構です。
とりあえず例からいきます。
Computation Expressionを使用するには、
(1)自分で定義した解釈ルール部分と
(2)解釈されるコード部分の2つが必要となります。
まずは(1)の自分で定義した解釈ルール部分を作成してみます。
 
type SyntacticTransformer1Builder() =
    member this.Return(x) =
        printfn "now return %d" x 
        x

これはクラスの定義です。メソッド名は、好きな名前で定義できるのではなく、制限があります。上のように書くと、解釈されるコード部分のreturn(先頭が小文字であることに注意)の解釈に上のメソッドが使用されます。

実際に解釈させるには、このクラスのインスタンスが必要となります。

let ts1 = new SyntacticTransformer1Builder()

解釈させるには、解釈される部分を{}で包み頭にこのインスタンス名をくっつけます。
例えば関数plusOneのなかでcomputation Expressionを利用すると次のようになります。

let backResult k =
    ts1 { return k }
   
使ってみます。

> backResult 4;;
now return 4
val it : int = 4

きちんと解釈され実行ができていることが分かります。
それでは内部的にどのような変換がなされたのでしょうか。

ここでは,ts1 { return k}の部分はts1.Return(k)と解釈されます。
つまり関数全体として
let backResult k =
    ts1.Retrun(k)
となるわけです。


ここでマトリョーシカを使った説明をしてみます。
let backResult k =
    ts1.Retun(k)

let backResult =
    (fun x -> ts1.Return(x)) //#1

となります。
一方普通の、引数をそのまま返す関数は
let backResultOrig =
    (fun x -> x)    //#2
   
となります。#1と#2を比べてみると、
#2は引数に適用すると、マトリョーシカが開いてその数がすぐ戻り値として返るのに対して#1は引数に適用すると、その数が関数ts1.Returnに渡されて、その戻り値が、backResultの戻り値となります。つまり、最後に値を返す前にワンクッション入るのです。
例えば引数3で考えると
#2では3->鍵穴->3 =中身(=戻り値)
となるのが
#1では3->鍵穴->3 ->ts1.Return関数 ->4 =戻り値となります。
この例では、最後にも戻り値を返す前にワンクッションが入りました。(ts1からの指示がありました。)
それでは、何層にもなっているマトリョーシカで、あるマトリョーシカを開いたときに、次のマトリョーシカをを開く前にワンクッション入れることはできないのでしょうか。これが次の話題です。

さて、ここまでは、解釈ルールは一つだけでした。それでは{}にでてくるキーワードすべてに解釈ルールを与えなければならないのでしょうか?
実は、F#のほとんどすべての書式に対するにデフォルトの暗黙変換ルールが定義されているので、「{}の中では(1)新しい型を宣言できない(2)mutableな値は使用できない」という制限以外は、{}内に普通のF#のコードを書くことができます。
それでは普通のF#のコードを{}内に追加してみます。

let plusOne k =
    ts1 { let u = k + 1 //let を追加した
          return (u)}

実行してみると、さっきと同じ結果が得られます。
それでは、letがどのようなルールで読み替えられるかを紹介したいと思います。letを自前のルールで読み替えるにはメソッドthis.Letを自前のクラス内で定義します。(VS2010 β2以降はこれはできなくなっているみたいです。)
次の定義をみてください。
    member this.Let (x,rest) =
        rest x

非常に奇妙な定義です。restとは一体何者なんでしょうか?

この定義の説明に入るまえにF#がts1を使い、{}内の式をどのように変換するかを見ておきます。
F#は

ts1 { let u = k + 1
      return (u) }

ts1.Let(k+1, (fun u ->
         ts1.Return(u)) と翻訳します。
        
別の例をあげると
ts1 {let x = 1
     let y  = x + 1
     return y + 1 }

ts1.Let (1,(fun x ->
            ts1.Let(x + 1,(fun y ->
                ts1.Return(y+1)))と翻訳します。

という形に変換してくれます。

このような変換を”継続渡し形式”を用いた変換といいComputation Expressionのキモです。

もとにもどると変換後の関数全体は次のようなります。

let plusOne k =
    ts.Let(k+1,(fun u ->
              ts1.Return(u)
          ))

ここでメソッドの定義をもう一度見てみます。

    member this.Return(x) =
      printfn "now we return %d" x
      x
   
    member this.Let (x,rest) =
      rest x

すなわち
Letはタプルを引数にとり、一成分を第二成分の関数に渡し計算を続ける(すなわち次の行に進む)というメソッドとして定義されています

よって関数plusOneに注釈をつけると、
**************************************************
let plusOne k =
    ts.Let(k+1,(fun u ->     //ts1.Letの定義より、uをk+1として次の行に進む
                             //        (k + 1に第二成分の関数を適用する)
              ts1.Return(u)    //ts1.Returnの定義より、引数を表示して、返す
          ))
**************************************************
とういうことで、期待通り動くことが分かります。

さて、this.Letを自前で再定義してしまうと、デフォルトのthis.Letが上書きされてしまい、{}内でletを使うたびに自前のthis.Letが使われることになるので、普通はthis.Bindメソッドの方を自分で定義します。これは{}内のletにが,this.Letで置き換えられるのと同様に、{}内のlet!(let bangと読むそうです)がthis.Bindで置き換えられます。
よってこちらを使うと先ほどの例は次のようになります。(一行だけprintfnを挿入してあります。)

type SyntacticTransformer1Builder() =
    member this.Bind (x,rest) =
        printfn "let!の右辺の値=%d" x
        rest x
   
    member this.Return(x) =
        printfn "now return %d" x
        x
  
let ts1 = new SyntacticTransformer1Builder()

let plusOne k =
    ts1 { let! u = k + 1
          return (u)}
         
実行例
> plusOne 8;;
let!の右辺の値=9
now return 9
val it : int = 9

例をみて、何か思ったことはあるでしょうか。
新しくprintfnを挿入した部分は、元のlet!の部分でいうと、右辺が評価されるところと、それを残りのコード部分へ渡す部分の間にあります。よって、ここに自前の作用をはさんでいるということになります。

実はリストのところで紹介したリスト内含表記も特殊化されたcomputation expressionです。yieldはコレクションの一部として値を返却して計算を継続するように、変換されます。


ここでマトリョーシカを使った説明をしてみます。

まずは通常のF#のコードでのlet文のマトリョーシカ文脈での解釈を説明します。

plusOne =
    (fun x ->
        let u = x + 1 //##1
        u
    )

##1の部分をこう解釈してみてください。
残りの部分をuという鍵穴をもったマトリョーシカで包んで、そのあと(x+1)の値を押しこむ。
別の言い方をすると、残りの計算をletの左辺の型の鍵穴をもったマトリョーシカで包んで、その横に(x+1)の値が吊っておく。(あくまで比喩です。)letの場合は有無を言わせず、吊ってある値を、鍵穴に押し込んで、計算を進める。(実はlet!の場合はおいおい説明しますが、ここで、そのまま鍵穴に押し込まずに、色々悪さをすることになります。)
このようにuという鍵穴をもつマトリョーシカをrestとしてとらえれば、this.Bindの意味合いがすこしとらえやすくなるのではないかと思います。
先ほどの例を再掲してみます。

type SyntacticTransformer1Builder() =
    member this.Bind (x,rest) =
        printfn "let!の右辺の値=%d" x
        rest x
   
    member this.Return(x) =
        printfn "now return %d" x
        x
  
let ts1 = new SyntacticTransformer1Builder()

let plusOne k =
    ts1 { let! u = k + 1 //##2
          return (u)}

上で##2の行は残りの部分をuという鍵穴をもったマトリョーシカでつつんで、
k+1とそのマトリョーシカをTs1.Bindに渡すとイメージできます。
このマトリョーシカがthis.Bind の引数のタプルの第2成分のrestを表します。
ここでは
   member this.Bind (x,rest) =
        printfn "let!の右辺の値=%d" x
        rest x
と定義されているので、x (=k+1)の値が表示された後、rest x で鍵穴に押し込まれマトリョーシカが割れて残りの計算が続きます。

ということで、let!の場合は、一度そこより後をマトリョーシカでつつんでから、Bindに渡すというイメージです。Bindからは、どう処理するかの指示が出ます。

Computatin expressionを非常に大雑把な言葉で比喩すると、
「通常のF#のコード+アルファ(let!など)」から「マトリョーシカ + 開いていく時にどうするかの指示」を作り上げる方法です。
(入れ子になっているそれぞれのマトリョーシカの鍵の横に、開く前に(鍵の横に吊ってある)鍵とマトリョーシカに対してどうするかの指示書が貼ってあるとイメージしてください。)
指示内容を定義するのが、上の例ではSyntacticTransformer1Builderクラスだということです。

ここでmember.Bindの型について説明しておきます。
これは  M<'a> * ('a -> M<'b>) -> M<'b> となっています。
M<'a>が押し込む鍵に相当します。(指示付きマトリョーシカで、最終的に'a型の値が返るもの)
右側の('a -> M<'b>)が、一番外側の鍵穴に'a型と書かれ、その内部に「指示付きマトリョーシカで、最終的に'b型の値が返るもの」が入ったマトリョーシカを表します。
 (例は'aがint 型で、'a -> M<b>が (fun u ->     ts1.Return(u)) を内部的に保持するobjectの場合です。)
 
またmember.Returnの型は'a->M<'a>となります。

ちなみに、Mはマトリョーシカ(指示付き)の頭文字のMです。(これは嘘で、ほんとはモナドのMです。)

さて宿題です。
type SyntacticTransformer1Builder() =
    member this.Bind (x,rest) =
        rest x
   
    member this.Return(x) =
        x
  
let ts1 = new SyntacticTransformer1Builder()

let MulOfPlusOne m n =
    ts1 { let! u = m + 1
          let! v = n + 1
          return (u * v)}
         
においてMulOfPlusOneがどう解釈されるか、ts1.Bindとts1.Returnを使った通常のコードに直してください。上の方の例の******************************の間のような形です。

スポンサーサイト

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

コメントの投稿

非公開コメント

プロフィール

T GYOUTEN

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

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

この人とブロともになる

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