LoginSignup
15
8

More than 5 years have passed since last update.

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

Posted at

F#はMicrosoftのDon Syme氏が開発したプログラミング言語で、命令型、オブジェクト指向、関数型の要素を併せ持つマルチパラダイム言語と位置付けられています。実装はオープンソースのMITライセンスのもと、githubのリポジトリで公開されています。

実行環境は.NET Frameworkおよび.NET CoreやMonoといったマルチプラットフォームに対応していますが、サポート状況については各プラットフォームごとの情報を参照してください(「F# の概要」など)。

Nullableって何ですか?

ここで取り上げるNullableはC#などでも提供されるSystem.Nullable<'T>のことです。これはNull許容型を表す型です。これにより、通常はnullに対応しない型でも、それに対応できるようになります。このような型はSystem.ValueTypeを継承した数値型のほか、EnumDateTimeなども該当します。

F#でのNullable

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

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

Nullableオブジェクト

Nullable<'T>'T型の値を持つNullableオブジェクトとして宣言できます。例えばNullable 11を持つNullableオブジェクトとなります。

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

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という例外が発生します。

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

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

デフォルト値の設定

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

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型と判定されるというのがあります。

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

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

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

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許容演算子を使えば算術演算や比較演算にも対応します。

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許容演算子とは、左辺と右辺のどちらか、あるいはいずれもがNullableオブジェクトの場合に利用できる演算子の総称です。大別すると算術演算と比較演算で提供されており、どれも通常の演算子とNullableオブジェクトに対応する側に?が並ぶ記号で表されます。これらはMicrosoft.FSharp.Linq.NullableOperatorsモジュールで実装されています。

算術演算のNull許容演算子

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

算術演算の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となります。

比較演算の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という例外が発生します。

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

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

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

unboxによるキャスト
open System

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

Nullable<'T>型が持つ値を別の型へのキャスト

Nullable<'T>が持つ値を別の型に変換するキャスト関数はMicrosoft.FSharp.Linq.Nullableモジュールに含まれており、Nullable.intなどで提供されます。これらはopen Microsoft.FSharp.Linqを実行すると利用できます。

Nullableを別の型に変換するキャスト
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となります。

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という例外が発生することを紹介しました。

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

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

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

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

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

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

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

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

[おまけ] 'T option型に変換する型拡張

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

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

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

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

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とみなされることがある

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

15
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
8