スポンサーサイト

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

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

今回はif expressionがらみでmember.Zeroを紹介します。
 
Computation Expressionで
 
else節ありの if expressionは
 
if expr then cexpr1 else cexpr2  が
 
if expr then ≪cexpr1≫ else ≪cexpr2≫ 
 
else節なしの if expressionは
 
if expr then cexpr が
 
if expr then ≪cexpr≫ else b.Zero() に解釈されます。
 
まずは例として
 
type IfBuilder () =
   member this.Bind(x,rest) = 
      rest x      
   member this.Return (x) = 
      x
   member this.Delay f = 
     printfn "delay"    
     f ()
   member this.Combine(a,b) =
     printfn "Combine"    
     printfn "a : %A" a;a 
     printfn "b : %A" b; b
   member this.Zero()  =
     printfn "zero"
     ()
 
let ifBld01 = new IfBuilder()
 
としておいて、
 
let testIf01 k =
            ifBld01{
                if k = 7 then 
                   printfn "It's  7"
                return k
            }
 
と定義し、5に適用させてみます。
 
> testIf01 5;;
delay
zero
delay
Combine
a : <null>
b : 5
val it : int = 5
 
これは、
 
if k = 7 then 
   printfn "It's  7"
 

 
if k = 7 then 
   printfn "It's  7"
else
    ifBld01.Zero()
 
となりこれがcexp1を表し
 
return k
 
がcexp2を表す場合ですから、前回やったようにCombineが登場します。
翻訳してみると
 
let testIf02 k =
    ifBld01.Delay(fun () ->
        ifBld01.Combine((if k = 7 then printfn "It's  7" else ifBld01.Zero()),
            ifBld01.Delay(fun ()-> 
                ifBld01.Return k)))
 
となります。実行してみると
 
> testIf02 5;;
delay
zero
delay
Combine
a : <null>
b : 5
val it : int = 5
 
というように、先ほどと同じ結果が出てきます。
 
(備考)
ただ
> testIf01 7;;
delay
It's  7
zero
delay
Combine
a : <null>
b : 7
val it : int = 7
となるので、実際にはifのどちらを採用してもZero()が呼び出されるようになっているみたいですので、
testIf02 は厳密な書き変えにはなっていないようです。
let testIf02 k =
    ifBld01.Delay(fun () ->
        ifBld01.Combine(((if k = 7 then printfn "It's  7";ifBld01.Zero()) else ifBld01.Zero()),
            ifBld01.Delay(fun ()-> 
                ifBld01.Return k)))
のような形でしょうか?。
 
それでは次に
let testIf03 k =
            ifBld01{
                if k = 7 then 
                   printfn "It's 7"
                else
                   printfn "It's not 7"
                return k
            }
 
を実行してみます。
 
> testIf03 5;;
delay
It's not 7
zero
delay
Combine
a : <null>
b : 5
val it : int = 5
 
> testIf03 7;;
delay
It's 7
zero
delay
Combine
a : <null>
b : 7
val it : int = 7
 
これもifBld01.Zero()は使われていないなずなのに、zeroと表示されます。
どーもif expr then cexpr1 else cexpr2およびif expr then cexpr1の形は、どの節が評価されようが、this.Zero()を返す形になっているみたいです。
 
Zeroの型はunit -> M<'a>です。
スポンサーサイト

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

コメントの投稿

非公開コメント

No title

et testIf02 k = → let testIf02 k =

>どの節が評価されようが、this.Zero()を返す形になっているみたい
MSのドキュメントに書いてあるのと違いますね。
私も不思議に思ったのでちょっと調べてみました。
例えば、(ib は、省略形)
ib { return (if k=7 then true else false) }
のような場合、Zero は、呼び出されません。
同じようでも
ib { ignore (if k=7 then true else false) }
だと、呼び出されます。
※ture, false を() に代えたり、else 節を無くしても同じ結果。
結局、if で値が消費される場合は、呼び出されず、消費されない場合は、呼び出される(else 節が有る無しには関係無い)
else 節が無い場合とは結局then 節と型を同じくするので、全体がunit を返すので、それが使われないような地の文の場合呼び出されるということだと思います。
また、その場合でも
if … then … else …
Zero
ではなく
if … then … ;Zero else … ;Zero

if … then …
の時は、
if … then … Zero
else Zero //これは追加される
みたいです。

No title

et testIf02 k = → let testIf02 k =  -> 修正しました。ありがとうございます。
どーもreturnなしで、最後がunitを返す形の場合は、最後にZeroがよびだされる仕様になっているようで、
ifBld01{do printfn "owari" }でも、this.Zero()が呼び出され、this.Zero()の返り値が、全体の返り値になるみたいです。
ですので、ib { ignore (if k=7 then true else false) } の場合はignoreの後が、なんであろうと(ifがあろうがなかろうが)Zeroが呼び出されるようです。
またif節が、関数の引数の場合は、computation expressionとしての変換は受けないので、this.Zero()の呼び出しはないと思われます。

No title

>if節が、関数の引数の場合は、computation expressionとしての変換は受けない
なるほど、それは、知りませんでした。
それはそれで、納得のような,おかしいような気がしますね。

>最後がunitを返す形の場合は、最後にZeroがよびだされる仕様
>this.Zero()の返り値が、全体の返り値
最後にというより
if 文を複数書いた場合、(あるいはdo 文も)
複数回Zero が呼び出されるので、
要するに、いわゆるUnit になるような地の文で、それぞれ、Zero が呼び出されるみたいですね。

No title

関数の引数の場合は、computation expressionとしての変換は受けない ->
let testIf04 k =
ifBld01{do printfn "%A" (if k = 7 then true else false)
return k
} や
let testIf04 k =
ifBld01{
let t = (if k = 7 then () else ()) //変な例
return t
}
というようなところでは,「ifの変換がない」というような意味です。
(ifがcomputation expression内の行の頭から、始まらない場合。)

あるいはdo 文も->例えば次のような場合Zeroは呼び出されません。
let testIf04 k =
ifBld01{
do printfn "1"
do printfn "2"
return k
}
これが、
let testIf04 k =
ifBld01{
do printfn "2"
}
となると,Zeroが登場するようです。
上は
b.Delay(fun () ->
b.Let((printfn "1"),(fun () ->
b.Let((printfn "2"),(fun () ->
b.Return(k))))))
下は
b.Delay(fun () ->
b.Let((printfn "2"),(fun () ->ここに対応するものなし )))
というようになるので

b.Delay(fun () ->
b.Let(printfn "2",(fun () ->b.Zero() )))
と変換されるのではないのかと思うのですが.....

No title

>次のような場合Zeroは呼び出されません。
なるほど、
私の前のコメントで、do でもその回数分呼び出されると言っているのは、間違いですね。
なんだか、釈然としませんね。
結局、現在のMSの説明は間違っているのは、確かですが、
・then の場合にZero が呼び出されるのが間違い
・どっちでも呼び出されるのに、else が省略された時だけ呼び出されるように書いているのが間違い。
どっちなんでしょうか?

No title

type IfBuilder () =
member this.Bind(x,rest) =
rest x
member this.Return (x) =
x
member this.Delay f =
printfn "delay"
f ()
member this.Combine(a,b) =
printfn "Combine"
printfn "a : %A" a;
printfn "b : %A" b;
member this.Zero() =
printfn "zero"
3 //ここに注目

let ifBld01 = new IfBuilder()としておいて
let testIf04 k =
ifBld01{
if k = 7 then
printfn "It's 7"
else
printfn "It's not 7"
return k
}
で、
> testIf03 5;;
delay
It's not 7
zero //zeroが呼び出されている
delay
Combine
a : 3 //これもzeroが呼び出されている証拠
b : 5
val it : unit = () //combineがunitを返すため

let testIf04 k =
ifBld01{
if k = 7 then
printfn "It's 7"
// else
// printfn "It's not 7"
return k
}

> testIf03 7;;
delay
It's 7
zero
delay
Combine
a : 3
b : 7
val it : unit = ()
となるので、ifの場合は、なんでもZeroが返るというのが正解だと思われます。

No title

>ifの場合は、なんでもZeroが返るというのが正解
(if としての値が消費されない)if で、Zero が、then でもelse でも(else が省略されても)呼び出されているというのは、既に、(もともと記事で)既知のことだと思いますけど、
前の質問で聞きたかったことは、
ドキュメントは仕様なので、実装が間違っている(動作がバグっている)のか、
そもそもドキュメントがおかしいのかということなんですが、
この動作が正しいということは、そもそも、if で、どっちにしろ、Zero が呼ばれることに [computation expression] でどういう意味があるんでしょうか?
いわゆる式ならびがunit 型であるように
[computation expression] として評価される式が、M<'a>型になるため
というような感じなのかなと思いますが、
それだとすると、return が int を返し、Zero が、() を返すような場合は、M<'a>型にそろえていないから(シンタックスエラーにならないのは)おかしいような気がします。
[computation expression] として解釈されるif でZero が呼ばれるのは、わかりますけど、
それがどのような意味があるのか具体的な例(こういう場合にこういう役に立つなければこうだから的な)を挙げていただけませんでしょうか?

No title

コメントの意図を誤解してしていたようで、すいません。私は次のように考えたのですが、どうでしょう。
まず、ifは評価結果が、combineの引き数のタプルの第一成分に渡されます。
combineはifだけでなく、For,Whileも引き受けます。例えばFor,Whileの結果が、unitではなく、関数にしたい場合、this.Zeroがifで呼び出されないとしたら、ifの評価結果として、いちいち関数を返すようにしなければならなくなります。
後日やろうとおもっているのですが、「programmingF#」という本のなかでは、StateモナドをF#で実装する場合
Zeroは
member this.Zero() =
StateFunc(fun s -> (),s)
Combineは
member this.Combine(p1:StateFunc<'s,unit>,p2:StateFunc<'s,'a>)=
StateFunc(fun s ->
let (),ns = Run p1 s
Run p2 ns
)

というように定義されています。

No title

>ifの評価結果として、いちいち関数を返すようにしなければならなくなります。
仰ることはわかりますが、
thenResultValue(), elseResultValue()
みたいに別々に返すのではなく、
then , else どっちを通っても同じZero を呼び出すのであれば、
あまり意味がない(他でまとめられそうなの)のでは?

No title

1つ前の私のコメント(2009-12-18 17:23 )の方針で、ちょっと例を変えてみました。
type ifMonad =
| IntValue of int
| NoValue

type IfBuilder () =
member this.Bind(x,rest) : ifMonad=
rest x
member this.Return (x) =
IntValue(x)
member this.Delay f =
printfn "delay"
f ()
member this.Combine(a,b) =
printfn "Combine"
printfn "a : %A" a;a
printfn "b : %A" b; b
member this.Zero() =
printfn "zero";NoValue

let ib = new IfBuilder()


let testIf01 k =
ib{
if k = 7 then return 7
}

実行例:
> testIf01 7;
delay
val it : ifMonad = IntValue 7
> testIf01 5;;
delay
zero
val it : ifMonad = NoValue
この場合、then 節で、Zeroは、呼ばれなくて、else 節でZero が呼ばれているのがわかります。

No title

この前の、コメントの例だと、Combine(連接) ができなくなります。
それは、
>ifは評価結果が、combineの引き数のタプルの第一成分に渡されます
ということ(return は、文としてunit 扱いだけど、if としては、値評価されるので、文法エラーになる
if だけの話じゃなく、
おかしな例だが
return 5
return 7
のような場合も同じ事情でエラーになる)
なんですけど、
結局のところ、
if で、連接ができる場合(<関数>でもダメな様な気がしますが)というのは、
if で書かれている内容が単なる文で、unit になるもの(その場合Zeroが両方呼ばれる)だけということになるから、
それなら、computation expression として評価せずに、単に
ignore <| if … then …
とした方がよい。
computation expression として評価する意味は、条件を調べてアクション(computation expression)を選択できる
というようなことだと思うので、
そういうcomputation expression として評価しないただの文を評価して、Combine できたところで、意味が無いように思いますが、どうでしょう?

No title

computation expression内のif文は、F#の地の文のifと同様の働き(命令型言語としてのアクションの選択しかできない。(処理の流れを返えることができない。))と、同様の感じではないかと思うのです。(使う方としては、地の文と同じ感覚で使えるというのがcomputation expressionの一つの売りなので。)
ということでBLUEPIXYさんのおっしゃる通り、ignore <| if … then … とか、do (if ... then ..)とかと、同じ働きしかしないし、わざとそのように実装してあるのでは、ないかと思うのです。
地の文の、unit型が処理の流れをかえないように、zeroという命名は、足し算の単位元としての0、(足しても、足し算としての結果を変えない)というところから来ているのかもしれません。
ということでcombineのタプルの第一引数に結果がくるというのは、確かに特には重要点ではない感じですね。

No title

>combineのタプルの第一引数に結果がくるというのは、確かに特には重要点ではない感じですね
そうですよね。
結局、Zero<Unit> しかCombineできないんだったら、if の結果がCombine に渡されること(if が最後の場合は当然Combine はされない)が何の意味が?

No title

if の結果がCombine に渡されることに何の意味が?->
よくは分からないですが、F#は地の文で、if,for,whileは()を返すという共通のの振る舞いをするようになってます。
computation expression内でも、if,for,whileがすべて、combineで扱われることによって、共通の振る舞いをさせるという思想ではないかとも思います。もしifを使うのであれば、逆に「for,whileもzeroか、zeroと同じ型を返さなければならない」という、制限が発生します。「自由よりも制限による統一を」という感じです。

No title

>逆に「for,whileもzeroか、zeroと同じ型を返さなければならない」という、制限が発生します
そうですね・・普段のF#でもたまに思うことですが、
書く式(文)並びで型をそろえないといけないというのは結構な制限のような気がします。

あと、これは聞いてはいけないような気がしますが・
IfBuilder のようなクラスは、いわゆる(Bind,Returnを実装する)モナドクラスなわけですよね。
このIfBuilder は、どんなモナドを表しているわけですか?

No title

IfBuilder は、どんなモナド->ごめんなさい不勉強で、Haskellはほとんどできないので、はっきり言って分かりません。(処理の流れ自体は変えてないので、たぶん名前などないのでは。<- あくまで想像です。)
プロフィール

T GYOUTEN

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

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

この人とブロともになる

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