式途中に null として評価されてしまうケースの回避方法
F# の話題です。
let x = A().B.C.D
とあるとき、もしA().B
が null
だったら?
F# で定義した型は AllowNullLiteralAttribute
属性を付与するか Unchecked.defaultof<'a>
を使用しない限り、たとえ参照型だろうが null
になることはありません。
でもCLRのクラスは null
の可能性があります。
.NET Framework の資産を使う以上、 NullReferenceException
と付き合っていく必要はどうしてもあります。
一番愚直な方法は、いわゆる nullチェックです。
let x = match A() with | null -> null | x -> match x.B with | null -> null | x -> match x.C with | null -> null | x -> x.D
…ツラすぎる。
次、Maybeモナドみたいなコンピュテーション式を使う方法。
[<AutoOpen>] module Maybe = type MaybeBuilder() = member __.Bind (x, f) = if x = null then null else f x member __.Return x = x let maybe = MaybeBuilder() let x = maybe { let! x = A() let! x = x.B let! x = x.C return x.D }
さっきよりは大分マシになりましたね。
まぁこれでいいんですが、もう一歩やってみました。
クッソ長いんですが、要は評価したい式を引用式として与えることで、途中で null
を検出したら全体を null
とするってことです。
null
を None
、非 null
を Some x
として見れば、まさにMaybeモナドがやっていることとニアリーイコールの結果を得られるわけです。
Gist のコメントに書いたやつのコピペですが、
open System open QuotationUtility let nullUri : Uri = null let result1 = Quotation.evaluate <@ "not null value" @> let result2 = Quotation.evaluate <@ (Uri("http://example.com/")).AbsoluteUri @> let result3 = Quotation.evaluate <@ (null : Uri).AbsoluteUri @> let result4 = Quotation.evaluate <@ nullUri.AbsoluteUri @> printfn "1 : [%A] / 2 : [%A] / 3 : [%A] / 4 : [%A]" result1 result2 result3 result4 (* 1 : ["not null value"] / 2 : ["http://example.com/"] / 3 : [<null>] / 4 : [<null>] *)
大体こんな感じになります。
なんとなーく動いてるように見えますね?
上の例で言えば let result = Quotation.evaluate <@ A().B.C.D @>
とすることで、仮に A().B
が null
だったとしても result
には null
が束縛されるだけです。もちろん D
まで正しく評価できれば D
の値が束縛されます。
たぶんバグあるので複雑な式を与えるとエラると思いますが、少なくとも単純な式なら null
を検出できているようです。
これを使えばスマートに nullチェックできるんじゃないでしょうか。
リフレクションなので、評価回数が増えてくると速度低下に繋がるところがネックですね。
ところで、F#のシンタックスハイライト、なんか毒々しいですね。