Nullableって何ですか? nullなんですか?

F#はMicrosoftの[Don Syme](https://twitter.com/dsyme "https://twitter.com/dsyme")氏が開発したプログラミング言語で、命令型、オブジェクト指向、関数型の要素を併せ持つマルチパラダイム言語と[位置付けられています](https://msdn.microsoft.com/visualfsharpdocs/conceptual/visual-fsharp "https://msdn.microsoft.com/visualfsharpdocs/conceptual/visual-fsharp")。実装はオープンソースの[MITライセンス](https://github.com/fsharp/fsharp/blob/master/License.txt "https://github.com/fsharp/fsharp/blob/master/License.txt")のもと、[githubのリポジトリ](https://github.com/fsharp/fsharp "https://github.com/fsharp/fsharp")で公開されています。

実行環境は.NET Frameworkおよび.NET CoreやMonoといったマルチプラットフォームに対応していますが、サポート状況については各プラットフォームごとの情報を参照してください(「[F# の概要](https://docs.microsoft.com/ja-jp/dotnet/fsharp/get-started/index "https://docs.microsoft.com/ja-jp/dotnet/fsharp/get-started/index")」など)。

# Nullableって何ですか?
ここで取り上げる`Nullable`はC#などでも提供される[System.Nullable<'T>](https://docs.microsoft.com/ja-jp/dotnet/api/system.nullable-1?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.nullable-1?view=netframework-4.7.1")のことです。これは[Null許容型](https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/nullable-types/ "https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/nullable-types/")を表す型です。これにより、通常は`null`に対応しない型でも、それに対応できるようになります。このような型は[System.ValueType](https://docs.microsoft.com/ja-jp/dotnet/api/system.valuetype?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.valuetype?view=netframework-4.7.1")を継承した数値型のほか、[Enum](https://docs.microsoft.com/ja-jp/dotnet/api/system.enum?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.enum?view=netframework-4.7.1")や[DateTime](https://docs.microsoft.com/ja-jp/dotnet/api/system.datetime?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.datetime?view=netframework-4.7.1")なども該当します。

# F#でのNullable
F#ではNullableにどう対応しているかを、以下のポイントから探ってみることにします。

* Nullableオブジェクト
* Null許容演算子
* Nullable対応のキャスト

## Nullableオブジェクト
[Nullable<'T>](https://docs.microsoft.com/ja-jp/dotnet/api/system.nullable-1?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.nullable-1?view=netframework-4.7.1")は`'T`型の値を持つ`Nullable`オブジェクトとして宣言できます。例えば`Nullable 1`は`1`を持つ`Nullable`オブジェクトとなります。

このオブジェクトの`HasValue`プロパティの値は`true`となり、何らかの値を持つことがわかります。そして`Value`プロパティでその値を得られます。

```fsharp:Nullableオブジェクトの生成
open System

let mutable n: Nullable<int> = Nullable 1  // Nullablleのインスタンス
n.HasValue  // true
n.Value     // 1
```

また、`Nullable<int> ()`などのように、`<'T>`で型を示したうえで引数なしで実行して得られたオブジェクトでは`HasValue`の値が`false`となり、`Value`で値を得ようとしても`System.InvalidOperationException`という例外が発生します。

```fsharp:Nullableオブジェクトの生成と代入の例
open System

n <- Nullable<int>()
n.HasValue  // false
n.Value     // 例外発生「System.InvalidOperationException: Null 許容のオブジェクトには値を指定しなければ なりません。」
```

### デフォルト値の設定
`Value`による例外を発生させないように、`GetValueOrDefault()`メソッドでデフォルト値を設定できます。引数なしなら0もしくは0.0(数値型による)、引数があれば、それがデフォルト値となります。

```fsharp:Nullableオブジェクトにおけるデフォルト値の設定
open System

let mutable n = Nullable<int>(1)  // 引数: 1
n.GetValueOrDefault()   // 1

n <- Nullable<int>()    // 引数なし
n.GetValueOrDefault()   // 0
n.GetValueOrDefault(3)  // 3
```

### Nullable型の判定
`Nullable<'T>`オブジェクトで注意すべき点として、型を調べると`'T`型と判定されるというのがあります。

```fsharp:Nullable型を調べるつもりが...
open System

let n1 = Nullable 1
n1.GetType() = typeof<int>    // true
printfn "%A" <| n1.GetType()  // "System.Int32"
```

そこでコードクォート(`<@ ... @>`)を使うと、`Nullable`かどうかと`'T`の型それぞれを確認できます。

```fsharp:Nullableと'Tの型を調べる
open System

let n1 = Nullable 1
// Nullable<'T>型かどうかを調べる
<@ n1 @>.Type = typeof<Nullable<int>>  // true
printfn "%A" <| <@ n1 @>.Type          // "System.Nullable`1[System.Int32]"
// 'T型が何かを調べる
<@ n1 @>.Type.GenericTypeArguments.[0] = typeof<int>    // true
printfn "%A" <| <@ n1 @>.Type.GenericTypeArguments.[0]  // "System.Int32"
```

### =, <>はデフォルトで対応
`Nullable`オブジェクトは、デフォルトで`=`, `<>`の演算に対応していますが、算術演算や大小の比較演算は対応していません。ですが、[Null許容演算子](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/symbol-and-operator-reference/nullable-operators "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/symbol-and-operator-reference/nullable-operators")を使えば算術演算や比較演算にも対応します。

```fsharp:Nullableオブジェクトでサポートされる演算
open System

(Nullable 2) =  (Nullable 2)   // true
(Nullable 2) <> (Nullable 3)   // true

(Nullable 2) <  (Nullable 3)   // サポートされない(Null許容演算子で対応)
(Nullable 2) +  (Nullable 3)   // サポートされない(Null許容演算子で対応)
```

## Null許容演算子
[Null許容演算子](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/symbol-and-operator-reference/nullable-operators "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/symbol-and-operator-reference/nullable-operators")とは、左辺と右辺のどちらか、あるいはいずれもが`Nullable`オブジェクトの場合に利用できる演算子の総称です。大別すると算術演算と比較演算で提供されており、どれも通常の演算子と`Nullable`オブジェクトに対応する側に`?`が並ぶ記号で表されます。これらは[Microsoft.FSharp.Linq.NullableOperatorsモジュール](https://msdn.microsoft.com/visualfsharpdocs/conceptual/linq.nullableoperators-module-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/linq.nullableoperators-module-%5bfsharp%5d")で実装されています。

### 算術演算のNull許容演算子
算術演算のNull許容演算子は、両辺のどちらかが`Nullable()`のときは結果も`Nullable()`となりますが、両辺ともに値を持つ`Nullable`オブジェクトであれば、計算の結果を持つ`Nullable`オブジェクトを得られます。

```fsharp:算術演算のNull許容演算子
open System
open Microsoft.FSharp.Linq.NullableOperators

(* 以下いずれも結果は Nullable 3 *)
(Nullable 1) ?+ 2              // 左辺がNullable
1 +? (Nullable 2)              // 右辺がNullable
(Nullable 1) ?+? (Nullable 2)  // 両辺がNullable

(* 以下いずれも結果はNullable<int>() *)
Nullable<int>() ?+ 2
1 +? Nullable<int>()
Nullable<int>() ?+? Nullable<int>()
```

### 比較演算のNull許容演算子
比較演算のNull許容演算子は、`Nullable`オブジェクトが持つ値どうしを比較します。結果は`bool`型です。`Nullable<'T>()`については、`<>`や同じ'T型どうしのとき`true`となります。

```fsharp:比較演算のNull許容演算子
open System
open Microsoft.FSharp.Linq.NullableOperators

(* 以下いずれもtrue *)
(Nullable 3) ?> 1              // 左辺がNullable
3 >? (Nullable 1)              // 右辺がNullable
(Nullable 3) ?>? (Nullable 1)  // 両辺がNullable

1 <>? (Nullable<int>())        // 右辺がNullable<int>()
(Nullable<int>()) ?=? (Nullable<int>())  // 両辺がNullable<int>()
```

## Nullable対応のキャスト
### Nullable<'T>型から'T型へのキャスト
F#ではキャスト(型変換)を行う関数が提供されていますが、これらは`Nullable`オブジェクトにも対応しています。ただし、処理の内容は`Value`と等価の`Nullable<'T> -> 'T`であって、`'T`を別の型には変換できません。また、引数が`Nullable<'T>()`のときは`System.InvalidOperationException`という例外が発生します。

```fsharp:Nullableオブジェクトをキャストする例
open System

Nullable 1 |> int       // 1
Nullable<int>() |> int  // 例外発生「System.InvalidOperationException: Null 許容のオブジェクトには値を指定しなければ なりません。」
```

同様の処理を行う関数には`unbox<'T>`もあります。こちらは引数が`Nullable<'T>()`のとき`System.NullReferenceException`という例外が発生します。

```fsharp:unboxによるキャスト
open System

Nullable 1 |> unbox<int>    // 1
Nullable<int> () |> unbox<int>    // 例外発生「System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。」
```

### Nullable<'T>型が持つ値を別の型へのキャスト
`Nullable<'T>`が持つ値を別の型に変換するキャスト関数は[Microsoft.FSharp.Linq.Nullable](https://msdn.microsoft.com/visualfsharpdocs/conceptual/linq.nullable-module-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/linq.nullable-module-%5bfsharp%5d")モジュールに含まれており、`Nullable.int`などで提供されます。これらは`open Microsoft.FSharp.Linq`を実行すると利用できます。

```fsharp:Nullable<'T>を別の型に変換するキャスト
open System
open Microsoft.FSharp.Linq

Nullable 1 |> Nullable.float           // Nullable<float> 1.0
Nullable 1 |> Nullable.float |> float  // 1.0 (float)
```

## nullなんですか?
実は、`Nullable<'T> ()`のように引数なしで生成された`Nullable`オブジェクトは`null`とみなされることがあります。実際に`defaultof`の戻り値は`null`となります。

```fsharp:Nullableオブジェクトのデフォルト
open System
open Microsoft.FSharp.Core.Operators.Unchecked

defaultof<Nullable>       // null
defaultof<Nullable<int>>  // null

defaultof<int>  // 0
defaultof<System.DateTime>  // 0001/01/01 0:00:00
```

そのため、`Nullable`オブジェクトの操作で例外が発生することがあります。

さきほどの`unbox<'T>`でも、引数が`Nullable<'T>()`のとき`System.NullReferenceException`という例外が発生することを紹介しました。

```fsharp:unboxによるキャスト(一部再掲)
open System

Nullable<int>() |> unbox<int>  // 例外発生「System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。」
```

また、この型を調べようとして、直接`GetType()`メソッドを実行しても同様の例外が発生してしまいますので注意が必要です。

```fsharp:引数なしNullableオブジェクトがnullとみなされる
open System

(Nullable<int>()).GetType()  // 例外発生「System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。」
```

ちなみに、このオブジェクトに対して`ToString()`メソッドを実行すると`""`(長さ0の文字列)となります。

```fsharp:引数なしNullableオブジェクトの文字列化
open System

(Nullable<int>()).ToString()    // ""(長さ0の文字列)
```

# [おまけ] 'T option型に変換する型拡張
F#のプログラムでは、結果を得られないかもしれないとき、例外の発生を回避するために`'T option`(もしくは`Option<'T>`)という型が使われることがあります。

さきほどの`unbox<'T>`では引数が`Nullable<'T>()`のとき例外が発生してしまいますが、`tryUnbox<'T>`という関数では、その際に`None`を返すことで例外の発生を回避します。

```fsharp:戻り値をOptionにして例外発生を回避
tryUnbox<int> (System.Nullable<int>())   // None (int option)
tryUnbox<int> (System.Nullable<int>(1))  // Some 1
```

そこで、F#の[型拡張](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/type-extensions "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/type-extensions")というしくみを利用して、`Nullable a`のように値を持つ場合は`Some a`とし、`Nullable<'T> ()`のように値を持たないときは`None`とする`ToOption`メソッドを追加する方法を考えてみました。型拡張による追加メソッドであれば、新たな`open`をせずに済み、メソッドで改めて`<'T>`を指定しなくてもオブジェクトに対して直接実行できるのが便利かと思います。

```fsharp:NullableからOptionに変換するToOptionメソッドの追加(型拡張)
type System.Nullable<'T when 'T : (new : unit -> 'T)
                         and 'T :> System.ValueType
                         and 'T : struct> with
  member this.ToOption() =
    if this.HasValue then Some this.Value else None

(* 使用例 *)
(System.Nullable 1).ToOption()       // Some 1
(System.Nullable<int>()).ToOption()  // None
```

---
最後に改めてF#における`Nullable`についての注意点をまとめると

* `Nullable<'T>`型が`'T`型と判定されることがある
* `Nullable`オブジェクトが`null`とみなされることがある

となります。こちらをご覧いただいた方々の参考になればと思います。