Consistency between `keys`, `values`, and `getindex` for `Pair` type

Consider

julia> nt = (;a = 10, b = 20)
(a = 10, b = 20)

julia> [(;k, v) for (k, v) in pairs(nt)]
2-element Vector{NamedTuple{(:k, :v), Tuple{Symbol, Int64}}}:
 (k = :a, v = 10)
 (k = :b, v = 20)

The above suggests that the keys of nt are Symbols. The values of nt are Ints. Indeed,

julia> keys(nt)
(:a, :b)

julia> values(nt)
(10, 20)

When a collection is getindexed with value in the key space, it produces a value in the value space:

julia> nt[:a]
10

So far so good.

I do not see such consistency within the Pair type itself.

julia> p = pairs(nt)
pairs(::NamedTuple) with 2 entries:
  :a => 10
  :b => 20

julia> [(;k, v) for (k, v) in pairs(p)]
2-element Vector{NamedTuple{(:k, :v), Tuple{Symbol, Int64}}}:
 (k = :a, v = 10)
 (k = :b, v = 20)

This suggests that the key space is Symbol and the value space is Int. However,

julia> keys(p)
(:a, :b)

julia> values(p) # hmm?
(a = 10, b = 20)

julia> p[:a]
10

Does the TODO in the definition of values(::Pairs) refers to this issue?

1 Like

I was trying to express an invariant that I expected to be violated:

julia> function check_invariant(collection)
         for k in keys(collection)
           collection[k] in values(collection) || return false
         end
         return true
       end
check_invariant (generic function with 1 method)

And thankfully,

julia> check_invariant(nt)
true

julia> check_invariant(pairs(nt))
true

I was confused because values(pairs(nt)) happens to include some keys (as helpfully pointed out by @ericphanson on Slack), but I see now why there is no inconsistency, because of the way that values(pairs(nt)) iterates.

I was updating some Julia code to not use kwargs.data to access the NamedTuple underlying splatted kwargs, and using values looked wrong, because it looked like value(kwargs) would not contain any key data (since values(::NamedTuple) does not either). This confused me.