web-dev-qa-db-ja.com

F#でのnull値の処理

C#コードとF#を相互運用する必要があります。 nullは指定可能な値であるため、値がnullかどうかを確認する必要があります。ドキュメントでは、パターンマッチングの使用を推奨しています。

match value with
| null -> ...
| _ -> ...

私が抱えている問題は、元のコードがC#で次のように構造化されていることです。

if ( value != null ) {
    ...
}

F#で同等の操作を行うにはどうすればよいですか?パターンマッチングのノーオペレーションはありますか? ifステートメントでnullをチェックする方法はありますか?

36

Nullの場合に何もしたくない場合は、ユニット値()を使用できます。

match value with
| null -> ()
| _ -> // your code here

もちろん、C#の場合と同じようにnullチェックを実行することもできます。この場合はおそらくより明確です。

if value <> null then
    // your code here
36
kvb

何らかの理由で(まだ調査していませんが)not (obj.ReferenceEquals(value, null))value <> nullよりもパフォーマンスが優れています。 C#から使用する多くのF#コードを記述しているので、nullを扱いやすくするために "interop" module を残しています。また、パターンマッチングの際に最初に「通常の」ケースを使用したい場合は、アクティブパターンを使用できます。

let (|NotNull|_|) value = 
  if obj.ReferenceEquals(value, null) then None 
  else Some()

match value with
| NotNull ->
  //do something with value
| _ -> nullArg "value"

単純なifステートメントが必要な場合は、これも機能します。

let inline notNull value = not (obj.ReferenceEquals(value, null))

if notNull value then
  //do something with value

更新

パフォーマンスの不一致に関するいくつかのベンチマークと追加情報を次に示します。

let inline isNull value = (value = null)
let inline isNullFast value = obj.ReferenceEquals(value, null)
let items = List.init 10000000 (fun _ -> null:obj)
let test f = items |> Seq.forall f |> printfn "%b"

#time "on"
test isNull     //Real: 00:00:01.512, CPU: 00:00:01.513, GC gen0: 0, gen1: 0, gen2: 0
test isNullFast //Real: 00:00:00.195, CPU: 00:00:00.202, GC gen0: 0, gen1: 0, gen2: 0

775%のスピードアップ-悪くないです。 .NET Reflectorのコードを見てみると、ReferenceEqualsはネイティブ/アンマネージ関数です。 =演算子は HashCompare.GenericEqualityIntrinsic<'T> を呼び出し、最終的に内部関数 GenericEqualityObj になります。 Reflectorでは、この美しさは122行のC#に逆コンパイルされます。明らかに、平等は複雑な問題です。 null- checkingの場合、単純な参照比較で十分なので、微妙な等価セマンティクスのコストを回避できます。

アップデート2

パターンマッチングでは、等価演算子のオーバーヘッドも回避されます。次の関数はReferenceEqualsと同様に機能しますが、F#の外部で定義された型または[<AllowNullLiteral>]で修飾された型でのみ機能します。

let inline isNullMatch value = match value with null -> true | _ -> false

test isNullMatch //Real: 00:00:00.205, CPU: 00:00:00.202, GC gen0: 0, gen1: 0, gen2: 0

アップデート3

Maslowのコメントに記載されているように、F#4.0では isNull operator が追加されました。上記のisNullMatchと同じように定義されているため、最適に実行されます。

54
Daniel

C#または.NETライブラリ(F#ではなく)で一般的に宣言されている型がある場合、nullはその型の適切な値であり、値をnullと簡単に比較できます。 kvbによる投稿。たとえば、C#の呼び出し元がRandomのインスタンスを提供するとします。

let foo (arg:System.Random) =
  if arg <> null then 
    // do something

C#の呼び出し元がF#で宣言された型を提供すると、事態はさらに複雑になります。 F#で宣言された型は値としてnullを持たず、F#コンパイラーはそれらをnullに割り当てたり、nullに対してチェックしたりできません。問題は、C#がこのチェックを行わず、C#の呼び出し元がnullを返す可能性があることです。例えば:

type MyType(n:int) =
  member x.Number = n

その場合、ボクシングまたはUnchecked.defaultOf<_>が必要です。

let foo (m:MyType) =
  if (box m) <> null then
    // do something

let foo (m:MyType) =
  if m <> Unchecked.defaultOf<_> then
    // do something
16
Tomas Petricek

もちろん、nullは一般にF#では推奨されませんが、...

例などに行かずに、いくつかの例を含む記事があります@ MSDN here =。 nullを検出する方法を具体的に明らかにします。そして、必要に応じて入力またはハンドルからそれを解析できます。

0
Hardryv

私は最近 similar dilemma に直面しました。私の問題は、C#コードから使用できるF#開発のAPIを公開したことです。 C#では、インターフェイスまたはクラスを受け入れるメソッドにnullを渡しても問題はありませんが、メソッドとその引数の型がF#型の場合、nullチェックを適切に実行することはできません。

その理由は、F#型は最初はnullリテラルを受け入れないためですが、技術的にはCLRの何もAPIが不適切に呼び出されることを防ぎます。特に、C#などのnullを許容する言語からは保護されません。 [<AllowNullLiteral>]属性を使用しない限り、最初は自分自身を適切にnull保護できなくなりますが、これは非常に醜いアプローチです。

したがって、私は 関連トピックのこのソリューション に到達しました。これにより、F#コードをクリーンでF#フレンドリーな状態に保つことができます。一般的には、任意のオブジェクトを受け入れてOptionに変換する関数を作成し、nullの代わりにNoneに対して検証します。これは、F#Option.ofObj関数で事前定義された関数を使用するのと似ていますが、後者では、渡されるオブジェクトを明示的にnullにできるようにする必要があります(したがって、醜いAllowNullLiteralアノテーションが付けられます)。

0
Ivaylo Slavov

私はこれを行う簡単な方法を見つけました

open System

Object.ReferenceEquals(value, null)
0
Nguyen Duc Dung