andyferris / Dictionaries.jl

An alternative interface for dictionaries in Julia, for improved productivity and performance
Other
282 stars 28 forks source link

support `dictionary(pair1, pair2, pair3, ...)` #50

Open schlichtanders opened 3 years ago

schlichtanders commented 3 years ago

just getting to know Dictionaries the very first thing which feels uncomfortable when coming from Base.Dict is that there is no support for something like Dict(:a => 1, :b => 2).

If I understand the interfaces correctly, this would be rather easy to add to the dictionary function, because so far it only supports a single argument call with something iterable.

Is there anything preventing the support of dictionary(:a => 1, :b => 2, ...)?

andyferris commented 3 years ago

I can totally see why this would be convenient.

Here Pair is not treated specially by dictionary(); we iterate the argument and from those elements we then get the first and second thing as key and value. This way we can use (key, value) tuples that come from zip for example.

Currently this means that dictionary((a => b) => (c => d)) already has a meaning equivalent to dictionary([a => b, c=> d]) (so there are two elements with keys a and c). Specialising on Pair would make this example become a single-element dictionary with key (a => b).

So two points I would make are:

  1. Making a change would be breaking, which is probably fine as my example is a little contrived.
  2. However, function specialization is most natural when it doesn't modify existing behavior from more generic methods. For example generic matrix multiplication is specialized to use BLAS - it returns the same result with a faster algorithm.

The second point is why this was not implemented yet. I'm not sure whether to favour convenience or neatness here. If anyone has an opinion it would be appreciated.

schlichtanders commented 3 years ago

thank you very much for the detailed insights.

Can you explain a bit more why 2. could become a problem here when constructing a Dictionary?. I guess that if you need fast dictionary construction, you just switch to one of the other constructors anyway, because your probably rather have a list of pairs or keys and values separately.

I guess dictionary(:a => 1, :b => 2) would in most of the cases only be used when creating a Dictionary for example purposes, where convenience is indeed very powerful to attract new users.

andyferris commented 3 years ago

would in most of the cases only be used when creating a Dictionary for example purposes

Yeah, exactly, while an extra [] is only two characters - dictionary([:a => 1, :b => 2]). Take Array or Vector for example, there's no multiple-value constructor there. There is however a literal syntax for arrays; along the same lines I'd love to have {:a = 1, :b = 2} or something like that in the future, but I'm not sure how feasible that is.

andyferris commented 3 years ago

Can you explain a bit more why 2. could become a problem here when constructing a Dictionary?

For example. one concern is that you could get sharp edges when you do dictionary(iter...) and iter happens to have exactly one thing, and it triggers some slightly different logic to when there are two or more things. I'm not sure if my worries are well-founded or imagined.

schlichtanders commented 3 years ago

Yeah, exactly, while an extra [] is only two characters

indeed and at least for me these two characters feel that much unnecessary that I am looking for another constructor which I may have overlooked to support the plain interface. That is what happened to me, not sure about how this generalizes to other users, but I guess at least some will stumble mentally.

schlichtanders commented 3 years ago

another good argument: Base.Dict probably had a discussion as well about whether they want to safe these two characters for convenience. They decided that it is worth. In addition people are now even used to having it.

schlichtanders commented 3 years ago

dictionary(iter...)

Say iter = [:a => 1], an iterator of Pairs. then with the new Pair support, dictionary(iter...) and dictionary(iter) would return the same.

Only when you have an iterator of something not supported but still iterable, and having an iterable as the one and only element. What could this be? Tuples?

looking at Tuples and Dict shows that there actually is a safe-guard for this

julia> Dict((:a,1), (:b,2))
ERROR: MethodError: no method matching Dict(::Tuple{Symbol, Int64}, ::Tuple{Symbol, Int64})

julia> Dict((:a,1))
ERROR: ArgumentError: Dict(kv): kv needs to be an iterator of tuples or pairs

julia> Dict((i for i in 1:2))  # apparently this check even applies to (some?) iterators
ERROR: ArgumentError: Dict(kv): kv needs to be an iterator of tuples or pairs

would such safe-guards be enough to solve your doubts?

andyferris commented 3 years ago

Potentially.

Perhaps one way to explain it is this: the way it is currently defined in terms of interfaces (iterate) with no reference to concrete types at all, which is generally how I try to design generic functions in Julia (as I think that’s how you create generic & composable code).

In this case I worry that we “should” have a third construction function, which is varargs, roughly from_pairs(kvs…).