検討:
> 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.tail
null
list.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
に作成するために使用する場合は、がであるががであるオブジェクトを提供します。そのため、値は出力されません。null
42::null
TailOrNull
null
HeadOrDefault
42
TL; DRUnchecked.defaultof
- F#タイプでは使用しないでください。あなたは何が起こるかを決して知りません。