F# Puzzle 2
問題
次のコードは何が出力されるでしょうか?
let toUpper (x: string) -> x.ToUpper() if true then "hoge" else "piyo" |> toUpper
解答
> if true then - "hoge" - else - "piyo" - |> toUpper;; val it : string = "hoge"
なんとtoUpper
されません。このコードの意図は、if式の結果をtoUpper
したいように感じますよね?
条件をfalseにすると大文字の値が返ります。
val it : string = "hoge" > if false then - "hoge" - else - "piyo" - |> toUpper;; val it : string = "PIYO"
この気持ち悪い挙動は、演算子を使った時に、値の先頭を揃えられる仕様が原因です。 F#にはオフサイドルールがあるのですが、演算子を使った場合は演算子の文字数+1文字より前から開始できます。 こう記述できます。
1 + 2 + 3
こんなこともできます。
let (+++++) x y = x + y 1 +++++ 2
今回の問題では、|> toUpper
の前行の"piyo"
のインデントが2で、パイプライン演算子の文字数が2のため、"piyo"
の行の続きと判断される訳です。
つまり、"piyo"
のインデントを4以上にすれば挙動が変わります。
> if true then - "hoge" - else - "piyo" - |> toUpper;; val it : string = "HOGE"
演算子と改行を組み合わせると、意図しない挙動になる場合があるので、気を付けようという話でした。
また、toUpper
の型がstring -> string
なので、この問題を発見しにくいです。
例えば、string -> int
な関数を呼ぼうとすると、コンパイルエラーになります。
> if true then - "hoge" - else - "piyo" - |> int;; |> int;; ---^^^ stdin(36,4): error FS0001: 型が一致しません。 string -> string という指定が必要ですが、 string -> int が指定されました。型 'string' は型 'int' と一致しません