komorebikoboshiのブログ

プログラミング記事(趣味レベル)が多め。

学ぶ!コンピューテーション式(3) ~return、return!

とりあえず分かるところから。

はじめに

この記事はコンピューテーション式の解説記事ではありません。以下学ぶ!コンピューテーション式(1) ~let!とBind - komorebikoboshiのブログと同文です。

本編に入る前に

このブログにもコメントしていただいたことのあるいげ太さんがコンピューテーション式についてつぶやかれていたので紹介を。



コンピューテーション式はモナド関連で語られることが多いですが、やりようによってはMyプログラミング言語みたいなのを作ることもできるようです。この「学ぶ!」シリーズでも、コンピューテーション式のそういう側面を深く学んでいけたらなあ、と思います。けっ、決してモナドが分からないからだとかそういうんじゃないんだからね!*1

今回やること

コンピューテーション式のreturn、return!(、yield、yield!)キーワードを見ていきます。また、Zeroメソッドもここでやろうかと。

return?

F#の関数ではreturnは使いません。最後に評価された式の値がその関数の返り値になります。

let add x y =
    x + y (* この行の評価結果が返り値になる *)

ですが、コンピューテーション式の中では、明示的にreturn(もしくはreturn!)を使って値を返さないといけません。

type HogeBuilder () =
    class
    end (* 冗長構文であえて空定義に *)

let hoge = new HogeBuilder()

let add x y = hoge{
    return x + y (* Returnメソッドがない、って怒られる *)
}

このように、returnキーワードを使うにはReturnメソッドを実装する必要があります。逆に言えば、Returnメソッドの実装次第でreturnの挙動を変えることができます。

type HogeBuilder () =
    member this.Return x = x + 1 (* return時に+1する *)

let hoge = new HogeBuilder()

let add x y = hoge{
    return x + y (* 実際は (x + y) + 1 *)
}
(* add 2 3 => 6 *)

で、Returnメソッドなんですが、モナドとからめて値を何かに包んで返すのが普通みたいです。

type MaybeBuilder () =
    member this.Return x = Some x (* Option型に包んで返す *)

けれど、入ってきた値をそのまま返しても問題はありません。(Identityモナドだと言い張りましょう)

return!

コンピューテーション式には、returnのほかにreturn!キーワードがあります。returnとの違いは、Returnメソッドの代わりにReturnFromメソッドが呼ばれるところです。

type HogeBuilder () =
    member this.Return x = Some x (* Option型で包む *)
    member this.ReturnFrom x = x (* そのまま *)

let hoge = new HogeBuilder()

let func1 x = hoge{
    return x
}

let func2 x = hoge{
    return! x
}

func1 "F#" (* => Some "F#" *)
func2 "F#" (* => "F#" *)

普通はreturn!の方は入ってきた値をそのまま返すように実装するみたいですが、例によって引数の数だけ合っていればいいので色々できます。

type HogeBuilder () =
    member this.ReturnFrom x = x.ToString() + "!!"

let hoge = new HogeBuilder()

let func1 x = hoge{
    return! x
}

func1 "F#" (* => "F#!!" *)

まあ使いやすいかは別として。

yieldとyield!

あとyieldとyield!っていうキーワードがありますが、それぞれreturn、return!と全く同じです。(呼び出されるメソッドは違いますが)

キーワード メソッド 変換方法
return Return return expr => builder.Return(expr)
yield Yield yield expr => builder.Yield(expr)
return! ReturnFrom return! expr => builder.ReturnFrom(expr)
yield! YieldFrom yield! expr => builder.YieldFrom(expr)

コンピュテーション式 (F#)
それなら要らないんじゃ、と思うのではなくて、これこそコンピューテーション式の真の力、自分自身で言語を設計できる醍醐味!と思えるようになりたいです。

じゃあreturnがなかったらどうなるの?

では普通のF#関数風にreturn(それに類するもの)を付けなかったら、というと、

type HogeBuilder () =
    member this.Return x = Some x
    member this.ReturnFrom x = x.ToString() + "!!"

let hoge = new HogeBuilder()

let add x y = hoge{
    x + y (* Zeroメソッドがない、って怒られる *)
}

こんな感じでZeroメソッドが必要になります。つまり何を返していいかわからないから、とりあえずZeroメソッドの結果を返り値にするよ、ということです。
また、Zeroメソッドはif~then~else式のelse句が省略されたときにも呼び出されます。(今回はその辺りはスルーします)

おわりに

以上で駆け足でしたがReturn、ReturnFrom、Yield、YieldFrom、Zeroという5つのメソッドと、それに対応する構文を見ていきました。ところで、このシリーズを書くにあたって大変参考にさせてもらっているステップアップでわかるコンピューテーション式。TryWith や TryFinally などの実装にぜひ活用したい Delayと Run - Bug Catharsisに、ちょっと気になるコードが。

  type MaybeBuilder() =
    member b.Bind(m, f) = Option.bind f m
    member b.Return(a) = Some a
    member b.ReturnFrom(m) = m
    member b.Combine(x, y) = x |> ignore; y

    // add
    member b.Delay (f) = f()

  let maybe = new MaybeBuilder()
  maybe { 
    let! c = Some "C#"
    let! fs = Some "F#"
    let! vb = Some "VB"
    let! cpp = Some "C++"

    return c
    return vb
    return cpp
    return fs}
  |> printfn "%A" 
  // Some "F#" 

これは、Some "F#"(return fs の評価値)を返すらしいのですが、ではその前の

return c
return vb
return cpp

この3つの式の結果はどこに消えたのでしょうか。……という謎を解き明かすために、次回はCombineメソッドについて勉強します!

*1:いや、現時点じゃさっぱりですけどね、モナド