💡

Null許容参照型がF#にも来るぞー

2024/09/15に公開

Null許容参照型

C# 8.0で導入された、Null許容参照型(Nullable Reference Types)がF#にも来るらしいです。

従来のNull対応

F#では従来からnull安全のための設計がされていました。基本的にnullを許さない方針で、代わりに用意されたOption型やValueOption型を使うように設計されています。どうしてもnullを扱う場合は、[<AllowNullLiteral>]属性を付けて明示するようになっていました。

しかし、F#がどんなに努力しても.NETクラスライブラリからはnullが送られてくるという現実からは避けられませんでした。

C# 8.0のNull許容参照型

C# / .NET本体が重い腰を上げ、Null許容参照型への対応をしました。これにより、.NETクラスライブラリからnullが送られないように、送られる場合は判別できるように改良されました。

C#ではコンパイラが制御フローを把握し、null値追跡をします。具体的にはnullチェックをするとその変数はnullでないと扱われるように切り替わります。

F# 9.0のNull許容参照型

これを受けて、F#でもNull許容参照型が再設計されました。従来より非nullが前提にあるため、C#のような変数が途中で扱いが変わるようなことはありません。

なお特徴を従来機能と比較しておくと

  • Option
    • インライン展開できないとGCが発生し、コストがかかる
    • Some nullnull値を保持できる
  • ValueOption
    • GCは発生しないがオーバーヘッドがある
    • ValueSome nullnull値を保持できる
  • Null許容参照型
    • コンパイル時に完結し、実行時にはオーバーヘッドがない
    • null値を保持できない

F#でのNull許容参照型の書き方

型の書き方

C# F#
string? string | null
string! string
string string

元々F#ではnullを認めていないので、C#におけるstring!が既定の動作です。

追加ライブラリ

これらのライブラリ関数を使うことで、Null許容参照型を扱うことになります。

val inline isNull: value:'T -> bool when 'T : not struct and 'T : null

従来からありましたが、nullかどうか判別します。boolで返されるので使い勝手が良くありません。

val inline nonNull : value: 'T | null -> 'T when 'T : not null and 'T : not struct

nullなら例外を出し、nullでないことを保証します。

val inline withNull : value:'T -> 'T | null when 'T : not null and 'T : not struct

Null許容参照型へ変換します。

val inline (|Null|NonNull|) : value: 'T | null -> Choice<unit, 'T> when 'T : not null and 'T : not struct

nullか判別するアクティブパターンです。

val inline (|NonNullQuick|) : value: 'T | null -> 'T when 'T : not null and 'T : not struct

nonNullと同様にnullでないことを保証します。

val inline defaultIfNull : defaultValue:'T -> arg:'T | null -> 'T when 'T : not null and 'T : not struct

nullの場合はデフォルト値を返すことでnullでないことを保証します。

val inline nullArgCheck : argumentName:string -> 'T | null -> 'T when 'T : not null and 'T : not struct

引数チェックで使用します。nullならArgumentNullExceptionを出し、nullでないことを保証します。

Optionとの比較

OptionValueOptionと比較した方が分かりやすいかな?

Option Null許容参照型
Some withNull / NonNull
None null / Null
defaultArg defaultIfNull
Option.get nonNull
Option.isNone isNull
Option.toObj withNull

Discussion