検討:
> Unchecked.defaultof<list<int>>;;
val it : int list = null
> 2 :: 1 :: Unchecked.defaultof<list<int>>;;
val it : int list = [2]
この動作の背後にある理由は何ですか?最終的なリストは[2;1]になるか、例外が発生することを期待します。
検討:
> Unchecked.defaultof<list<int>>;;
val it : int list = null
> 2 :: 1 :: Unchecked.defaultof<list<int>>;;
val it : int list = [2]
この動作の背後にある理由は何ですか?最終的なリストは[2;1]になるか、例外が発生することを期待します。
これはかなり驚くべき質問です。F#リストがどのようにコンパイルされているかを調べ、その動作を説明しています。私はこれをバグとは見なしません(それがモジュールにある理由defaultofですUnchecked、それはあなたのコンピュータを焼き尽くすかもしれません!)
まず、空のリスト[]はF#2.0のように表されませんnull。これは、空のリストでメソッドを呼び出すことができなくなり、多くのことが壊れてしまうためです(空のリストをSeq関数に渡すなど)。余談ですが、None値はnull効率として表されます。つまり、意味があるとしても、インターフェイスoption<'a>を実装することはできません。seq<'a>
したがって、代わりに、F#は特別な値を使用してlist.Empty空のリストを表します。これで、この値は空でないリストを表す値と同じタイプのインスタンスになります(タイプはFSharpList<'T>)。唯一の違いは、インスタンスのheadとtailフィールドの両方がであるということnullです。
これで、これがどこに向かっているのかがわかるかもしれません。空のリストは、基本的にthis.tailフィールドがあるオブジェクトにコンパイルされますnull(this.headフィールドnullもそうですが、これはそれほど重要ではありません)。
フィールドがである場合(これは空のリストの末尾を取得していることを意味するため、これはエラーです)、list.Tailプロパティは例外をスローしますが、チェックせずに値を返し、コンパイルに使用される内部プロパティもありますパターンマッチング。this.tailnulllist.TailOrNull
たとえば、次のような単純なiter関数があります。
let rec iter (xs:int list) =
match list with
| x::xs -> Console.WriteLine(x); iter xs
| [] -> ()
これは、次のC#コードにコンパイルされます。
if (list.TailOrNull != null) {
Console.WriteLine(list.HeadOrDefault);
iter(list.TailOrNull);
}
これlistは、が空のリストであるかどうかをチェックすることにより、が空のリストであるかどうかをチェックしlist.TailOrNullますnull。listその場合、現在の値がlist.Empty空のリストを表す値であることがわかります(これもそうHeadOrDefaultであると想定しているため、出力されませんnull)。
値を明示的defaultofに作成するために使用する場合は、がであるががであるオブジェクトを提供します。そのため、値は出力されません。null42::nullTailOrNullnullHeadOrDefault42
TL; DRUnchecked.defaultof - F#タイプでは使用しないでください。あなたは何が起こるかを決して知りません。