Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PersistentHashMap.containsKey is broken for null values #85

Open
iainnicol opened this issue Jan 24, 2018 · 5 comments
Open

PersistentHashMap.containsKey is broken for null values #85

iainnicol opened this issue Jan 24, 2018 · 5 comments

Comments

@iainnicol
Copy link

PersistentHashMap.containsKey returns false negatives, if the value of the key-value pair is null.

This can also manifest if the value is None, or the value is unit. In fact, I discovered this because I was trying to treat PersistentHashMap<'a, unit> like a persistent hash set. This is because, internally, .NET stores None and unit as null.

Example:

open FSharpx.Collections

[<EntryPoint>]
let main (argv : string array) : int = 
    let key = "key"
    let value = None // or ()
    let map = PersistentHashMap.add key value PersistentHashMap.empty
    // Assertion fails
    assert (PersistentHashMap.containsKey key map)
    0
@jackfoxy jackfoxy added the bug label Jul 7, 2018
@jackfoxy
Copy link
Contributor

jackfoxy commented Jul 7, 2018

I think this is an issue with the HashMap architecture, and probably has a similar issue with keys.
The only fix I can think of would be altering the underlying Array to bool * obj pairs to indicate presence. I don't know if that would impact performance enought to make it an undesirable change.

The other alternative is to find an elegant way to prevent option types.

@iainnicol
Copy link
Author

iainnicol commented Jul 9, 2018

Thanks.

bool * obj sounds sensible. Maybe obj option could also work, because Some null cannot be represented by null.

Or if the (potential) performance is a problem, we could just exception guard the add function.

On your suggestion of preventing option types, we could add a and 'S : struct constraint. Unfortunately, that would be overly restrictive, given that unions do not default to being structs.

It is a shame that F# has a null constraint, because what we really need is a not null constraint. Edit: actually, that would not help either. null is not a "proper value" of an option type, despite null being an internal representation of None.

@Ivan-Tigan
Copy link

Ivan-Tigan commented Mar 1, 2020

This is terrible. It is quite disappointing when the Option type fails at the only thing it's meant to do. Hope it gets fixed.
In the meantime here is a hack for anyone who still wants to use Options.
(It should only override functionality for case when you use Options and no other case)

type PersistentHashMap() =
    static member containsKey key hm = try hm |> PersistentHashMap.find key |> fun x -> Option.isSome x || Option.isNone x with _ -> false

@glchapman
Copy link

It looks to me like the problem here is with INode.find. Note that Clojure has two overloads for this method; the one used for containsKey takes a distinct NOT_FOUND object which is used to detect when the key is not found. Anyway, it looks like INode.find (in current FSharpx.Collections) is only ever used by ContainsKey, so why not change the result type of INode.find to bool and make the appropriate changes to the node implementations? Since INode is internal to the library, I believe that shouldn't be a breaking change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants