NaNとMap
先日、JavaScriptで以下の挙動になるのはなんでだろうねという話になった。
> m=new Map(); Map {} > m.set(Number.NaN, 0) Map { NaN => 0 } > m.set(Number.NaN, 1) Map { NaN => 1 } > m.set(Number.NaN, 2) Map { NaN => 2 } > Number.NaN === Number.NaN false > Number.NaN !== Number.NaN true
そして理由はMDNに書かれていた。
厳格な等価性では NaN を他のどの値 (自分自身も含む) とも等しくないものとして扱います
https://developer.mozilla.org/ja/docs/Web/JavaScript/Equality_comparisons_and_sameness
NaN は NaN と同じとみなされ (NaN !== NaN であっても)、他の値はすべて === 演算子の意味に従って等価性が考慮されます
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map
めでたしめでたし…いや、もうちょっとだけ続くんじゃよ。
.NETのDictionaryとF#のMap
ふと、ほかの…例えば.NETのDictionaryやF#のMapはどうなるのか気になったので、Try F#上で試してみた。
open System.Collections.Generic // nanの定義 // https://github.com/dotnet/fsharp/blob/6a8885ff1152db81ab37b94f048a01e88b8847d6/src/fsharp/FSharp.Core/prim-types.fs#L3758 Map.empty |> Map.add nan 1 |> Map.add nan 2 |> printfn "%A" let d = Dictionary<float, int>() d.Add(nan, 1) d.Add(nan, 2) d |> Seq.iter (fun kv -> printfn "%A" kv) nan = nan |> printfn "%b" nan <> nan |> printfn "%b"
map [(NaN, 1); (NaN, 2)] NaN,2 false true
もうちょっと深堀りする必要がありそうだ。
Dictionary
DictionaryはSystem.IEquatable<T>
が実装されていればそれを使うことになっている。
F#におけるfloatは.NETでいうところのdoubleだ。
DoubleはIEquatable<double>
が実装されており、NaN == NaN
はtrueとなるよう実装されている。よってDictionaryでは値が上書きされる。
F# Map
F#のMapのkeyはIComparer<'T>
を使って比較される。
今回はMap.empty
に要素を追加していったので、Map.empty
が用意したLanguagePrimitives.FastGenericComparer<'T>
がcomparerとして使われる。
もっと追ってみる。
GenericComparison
を使っている関数は他にcompare
があるので、こいつで何を返しているか見てみよう。
compare nan nan |> printfn "%d"
1
ということで、別のkey扱いである。
もっと実装を追いたいなら以下を読み進めていけばよいはず。
ちなみに、NaNだとkeyが一致しないので値は取得できない。
let d = Map.empty |> Map.add nan 1 |> Map.add nan 2 // Noneになる d |> Map.tryFind nan |> printfn "%A"