Java has a specific hack for this which I discovered by accident a few years ago. Normally, given (primitive) double values d0 and d1, then boxing them does not affect equality testing - i.e. d0 == d1 if and only if Double.valueOf(d0).equals(Double.valueOf(d1)). However if d0 and d1 are both NaN, then the boxed versions ARE considered equal while d0 == d1 is false.<p>This inconsistency infuriated me when I discovered it but the Javadoc for Double.equals explicitly states that this anomaly is there to "allow hash tables to work properly".
The issue with NaN equality is interesting, but is that really why they're adding a clear(x) builtin? What if you want to remove a single-NaN key from a map? clear(x) seems like a band-aid at best if the Go team is strictly trying to fix removing NaN keys in maps.
> This for loop is less efficient than clearing a map in one operation<p>For maps with keys that are reflexive with == the Go compiler already optimizes the range loop to a single efficient runtime map clear call: <a href="https://go-review.googlesource.com/c/go/+/110055" rel="nofollow">https://go-review.googlesource.com/c/go/+/110055</a>
Interestingly JavaScript handles this correctly and specified Object.is equality that is different than `===` equality around NaNs so `const m = new Map(); m.set(NaN, 3); m.get(NaN)` returns 3 and `.delete` similarly works.
I'm curious why this is not implemented as a method on the map type (and others like list) instead of being a top-level builtin. I suppose it is consistent with other collection operations such as append and len... which I guess makes me wonder why those are builtins as well.
In Java, the primitive type double uses the IEEE-754 semantics and java.lang.Double uses the bits by bits comparison semantics [1] so List<Double>.remove() works correctly.<p>[1] <a href="https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/lang/Double.html" rel="nofollow">https://docs.oracle.com/en/java/javase/19/docs/api/java.base...</a>
Usage of NaN as hash key reminds me of the two uses of NULL in SQL. One is as unknown values which are never equal to anything even other NULL values. Another use is as a key for aggregate grouping. In that case the entry represents the aggregate for all the unknown values which aren't equal but still grouped together. Different uses have different meanings so invalid in one use does not invalidate other uses.
You've already got zero and negative zero as an outlying case. I'm not sure why anyone would feel comfortable using floats as keys in a map. To anyone who has done this.. why? What was the use case?
C# had the same NaN behavior until recently: <a href="https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/7.0/equals-nan" rel="nofollow">https://learn.microsoft.com/en-us/dotnet/core/compatibility/...</a>
Meh using floating point as keys for maps in any language is just asking for trouble -- it’s not just NaN<p>I don’t think there’s any real use case for it<p>I’d say clear() is good for clarity, and that’s it
This looks like catching up to GNU Awk, which allows "delete a" to clear an associative array. That's an extension over POSIX, which specifies "delete a[key]".
> <i>This for loop is less efficient than clearing a map in one operation</i><p>That is not obvious; a compiler could have a pattern match for that exact AST pattern, and transform it to a delete operation. (Except for that pesky issue where two fail to be equivalent due to NaN keys.)<p>Quick and dirty, not entirely correct proof-of-concept in TXR Lisp:<p><pre><code> 1> (macroexpand '(my-dohash (k v some.obj.hash) (print [some.obj.hash k])))
(dohash (k v some.obj.hash
())
(print [some.obj.hash
k]))
2> (macroexpand '(my-dohash (k v some.obj.hash) (del [some.obj.hash k])))
(clearhash hash)
</code></pre>
Impl:<p><pre><code> (defmacro my-dohash ((kvar vvar hash : result) . body)
(if-match @(require ((del [@hash @kvar]))
(null result))
body
^(clearhash hash)
^(dohash (,kvar ,vvar ,hash ,result) ,*body)))</code></pre>
OK, now I understand why OCaml's Float.compare function compares NaNs as equal to each other: <a href="https://discuss.ocaml.org/t/assertions-involving-nan/10762/4" rel="nofollow">https://discuss.ocaml.org/t/assertions-involving-nan/10762/4</a>
I'm more surprised they went with a new built-in instead of making the compiler recognize something like<p><pre><code> m = make(map[A]B, cap(m))
</code></pre>
just like it already recognized<p><pre><code> for k := range m {
delete(m, k)
}
</code></pre>
and many other similar idioms.<p>(cap because len is not quite the same -- note, cap is not currently defined on maps)<p>EDIT: Likely because that's an assigment on m, not a mutation of it, so it can't be done e.g. in a function that gets m as argument.
That was weird. Makes sense once you know about NaN's incomparability, but still surprising.<p>I had to try it in Python (3.10.4, Windows on x86), it worked fine:<p><pre><code> >>> import math
>>> a={math.nan: 1}
>>> a
{nan: 1}
>>> del(a[math.nan])
>>> a
{}</code></pre>
Hold on a sec, the whole clearing map scenario. Is it for reusing within your fn or is it trying to free memory? If latter, I’ve just been hoping GC does its job