コンピュテーション式で"はちみーのうた"

コンピュテーション式でキーワード引数 - ぐるぐる~カスタムオペレーションの呼び出し順序を制御する - Qiita をみていたら久々にコンピュテーション式で遊びたくなったのでひとネタ供養。

今回のネタは状態遷移を書けると噂の”はちみーのうた”です。 歌詞通りにキーワードを呼び出さないとコンパイルエラーになるはず、きっと。 まぁ多少の妥協があります。

type GotoHachimi = interface end
type GotoAshi = interface end

type Hachimi = Hachimi
  with
    override _.ToString() = "はちみー"
    interface GotoHachimi

type Hachimi<'T when 'T :> GotoHachimi> = Hachimi of 'T
  with
    override this.ToString() =
      match this with
      | Hachimi t -> sprintf $"{t}はちみー"
    interface GotoHachimi

type Nameru<'T when 'T :> GotoHachimi> = Nameru of 'T
  with
    override this.ToString() =
      match this with
      | Nameru t -> sprintf $"{t}をなめると"
    interface GotoAshi
    interface GotoHachimi

type Ashi<'T when 'T :> GotoAshi> = Ashi of 'T
  with
    override this.ToString() =
      match this with
      | Ashi t -> sprintf $"{t}あしがー"
    interface GotoAshi

type Hayakunaru<'T when 'T :> GotoAshi> = Hayakunaru of 'T
  with
    override this.ToString() =
      match this with
      | Hayakunaru t -> sprintf $"{t}はやくーなる"
    interface GotoHachimi

type Lyrics = Nameru<Hachimi<Hachimi<Hachimi<Hachimi<
  Nameru<Hachimi<Hachimi<Hachimi<Hachimi<
    Hayakunaru<Ashi<Ashi<Ashi<Nameru<Hachimi<Hachimi<Hachimi<Hachimi>>>>>>>>
  >>>>>
>>>>>

type HachimiSongBuilder () =
  member _.Yield(()) = ()

  // 右辺が `Hachimi` だけだと `'T -> Hachimi<'T>` を返してしまうので、 `Hachimi.Hachimi` で固定できるようにしておく
  [<CustomOperation("はちみー")>]
  member _.Hachimi(()) = Hachimi.Hachimi

  [<CustomOperation("はちみー")>]
  member _.Hachimi(x: #GotoHachimi) = Hachimi(x)

  [<CustomOperation("をなめると")>]
  member _.Nameru(x: Hachimi<Hachimi<Hachimi<#GotoHachimi>>>) = Nameru(x)

  [<CustomOperation("あしがー")>]
  member _.Ashi(x: #GotoAshi) = Ashi(x)

  [<CustomOperation("はやくーなる")>]
  member _.Hayakunaru(x: Ashi<Ashi<Ashi<Nameru<_>>>>) = Hayakunaru(x)

  member _.Run(song: Lyrics) = printfn $"{song}"

let はちみーのうた = HachimiSongBuilder()

はちみーのうた {
  はちみー; はちみー; はちみー
  はちみー; をなめると
  あしがー; あしがー; あしがー
  はやくーなる

  はちみー; はちみー; はちみー
  はちみー; をなめると
  
  はちみー; はちみー; はちみー
  はちみー; をなめると
}

出力:

はちみーはちみーはちみーはちみーをなめるとあしがーあしがーあしがーはやくーなるはちみーはちみーはちみーはちみーをなめるとはちみーはちみーはちみーはちみーをなめると
  • 初回はかならず”はちみー”が来てほしいので単発の Hachimi が来るように YieldHachimi メソッドを用意
  • カスタムパラメータはoverloadが可能
  • 繰り返しを雑に省略定義するために interface を使用
  • はちみー を 4回繰り返さないと をなめると に移動できないように引数の型を調整
    • あしがー 3回も同じ方法
  • Run メソッドの引数を Lyrics 型に限定することで、歌詞通りなことを保証する
  • 型パラメータに制約をつけているけど、なくてもたぶん問題ない

久しぶりの記事がこんなのでいいのだろうか :thinking_face: