I/Oなどの、失敗することがある一連の処理を記述する場合、C系列の手続き型言語では以下のように書くことが多い。
bool func0(...) { if (!func1(...)) { return false; } if (!func2(...)) { return false; } if (!func3(...)) { return false; } 肝心な処理; return true; }
どうやら、これには「途中リターン」などと名前が付いているようで、コーディング規約で禁止されていたりなど一部の界隈では禁じ手となってたりするようだ。まぁ、GOTOの一種なのだが、「途中リターン」を禁じるなら本質的に同じである「途中ブレーク」も「途中コンティニュー」も禁ずるべきで、そんな手枷足枷でプログラムを書くのは苦行である。
ML系列の関数型言語、OCamlやF#やHaskelは普通に書くと「途中リターン」も「途中ブレーク」も「途中コンティニュー」もない。C系列の関数型言語のScalaやKotlinにはある。
「途中リターン」なしをC系列で表現すると、以下のような書き方になる。
bool func0(...) { bool ok; if (func1(...)) { if (func2(...)) { if (func3(...)) { 肝心な処理; ok = true; } else { ok = false; } } else { ok = false; } } else { ok = false; } return ok; }
「途中リターン」を使う最大の理由は、「肝心な処理」がネストの奥深くになることを避けることであり、読みやすさ向上のためのささやかな手法に過ぎない。
F#で普通に表現すると以下のようになる。
let func0 (...) = if func1 (...) then if func2 (...) then if func3 (...) then 肝心な処理 ; true else false else false else false
オライリーの「プログラミングF#」でのコンピュテーション式の例がこの「途中リターン」の実現となっている。
type OptionResultBuilder() = member this.Bind (x, rest) = match x with | Some (r) -> rest r | None -> None // 一度NGになったら後続の処理は行わない。 member this.Return (x) = x let boolOption (b: bool) = if b then Some (b) else None // falseをNGとする let optionResult = OptionResultBuilder() let func0 (...) = optionResult { let! _ = boolOption(func1 (...)) in let! _ = boolOption(func2 (...)) in let! _ = boolOption(func3 (...)) in 肝心な処理 ; return boolOption(true) }
とまぁ、こんな風に書けるわけで、確かに「肝心な処理」がネストの奥深くになることは避けられたが、読みやすさは向上しているだろうか。OptionResultBuilder型のBind関数とlet!の関係を知らなければ読みようがないコードになっており、「途中リターン」実現で得られるささやかな利益よりはコンピュテーション式導入での絶大なる不利益の方が上回っていると言えそうだ。
2020.09.09
OSTRACISM CO.
OSTRA / Takeshi Yoneki