Listコンピュテーション式にyield breakもどきを作れないかあがいてみた

コンピュテーション式におけるreturnとyieldにも書かれている通り、カスタムオペレーターで yield break を実装することはできません。では、型の力をつかってどうにかできないか、とあがいてみました。

Builder の定義

前述の記事で掲載されている ListBuilder の定義を少し変更します。なお、下記コードでは変更のない部分を省略しています。

module Control =
    type YieldControl<'a> = Break | Return of 'a

open System
open Control // オープンする順序で Break の優先順位を決める
open Basis.Core.ComputationExpr

type ListBuilder internal () =

  ...

  member this.YieldFrom(x) =
    match x with
    | YieldControl.Break -> (this.Zero() |> fst, Break)
    | YieldControl.Return x -> this.Return(x)

  ...

すると、

open Control

list {
  for x in [1; 2; 3; 4; 0; 5; 6] do
    if x = 0 then
      yield! Return -1
    yield x
  return 10
} // equal [1; 2; 3; 4; -1]

list {
  for x in [1; 2; 3; 4; 0; 5; 6] do
    if x = 0 then
      yield! Break
    yield x
  return 10
} // equal [1; 2; 3; 4]

と、ちょっと残念な形で yield break (ついでに yield return)もどきを記述できます。

Yield メソッドを使えない理由

既存の Yield は 'a -> 'a list * FlowControl です。仮に YieldControl<_> -> 'a list * FlowControl を定義したとしても、呼び出し側でどちらを呼べばいいかの型注釈をつける必要がでてきます。

YieldFrom は list を受け取るメソッドしか存在しないため、どのメソッドを呼び出せばよいか判別できます。なので型注釈も必要ありません。

結論

定義が複雑になるくらいなら、素直に return! [] を使ったほうがよさそうですね。