-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
add NamedTuples #22194
add NamedTuples #22194
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
# This file is a part of Julia. License is MIT: https://julialang.org/license | ||
|
||
""" | ||
NamedTuple{names,T}(args::Tuple) | ||
|
||
Construct a named tuple with the given `names` (a tuple of Symbols) and field types `T` | ||
(a `Tuple` type) from a tuple of values. | ||
""" | ||
function NamedTuple{names,T}(args::Tuple) where {names, T <: Tuple} | ||
if length(args) == length(names) | ||
if @generated | ||
N = length(names) | ||
types = T.parameters | ||
Expr(:new, :(NamedTuple{names,T}), Any[ :(convert($(types[i]), args[$i])) for i in 1:N ]...) | ||
else | ||
N = length(names) | ||
NT = NamedTuple{names,T} | ||
types = T.parameters | ||
fields = Any[ convert(types[i], args[i]) for i = 1:N ] | ||
ccall(:jl_new_structv, Any, (Any, Ptr{Void}, UInt32), NT, fields, N)::NT | ||
end | ||
else | ||
throw(ArgumentError("Wrong number of arguments to named tuple constructor.")) | ||
end | ||
end | ||
|
||
""" | ||
NamedTuple{names}(args::Tuple) | ||
|
||
Construct a named tuple with the given `names` (a tuple of Symbols) from a tuple of | ||
values. | ||
""" | ||
function NamedTuple{names}(args::Tuple) where {names} | ||
NamedTuple{names,typeof(args)}(args) | ||
end | ||
|
||
""" | ||
NamedTuple{names}(nt::NamedTuple) | ||
|
||
Construct a named tuple by selecting fields in `names` (a tuple of Symbols) from | ||
another named tuple. | ||
""" | ||
function NamedTuple{names}(nt::NamedTuple) where {names} | ||
if @generated | ||
types = Tuple{(fieldtype(nt, n) for n in names)...} | ||
Expr(:new, :(NamedTuple{names, $types}), Any[ :(getfield(nt, $(QuoteNode(n)))) for n in names ]...) | ||
else | ||
types = Tuple{(fieldtype(typeof(nt), n) for n in names)...} | ||
NamedTuple{names, types}(Tuple(getfield(nt, n) for n in names)) | ||
end | ||
end | ||
|
||
NamedTuple() = NamedTuple{(),Tuple{}}(()) | ||
|
||
length(t::NamedTuple) = nfields(t) | ||
start(t::NamedTuple) = 1 | ||
done(t::NamedTuple, iter) = iter > nfields(t) | ||
next(t::NamedTuple, iter) = (getfield(t, iter), iter + 1) | ||
endof(t::NamedTuple) = nfields(t) | ||
getindex(t::NamedTuple, i::Int) = getfield(t, i) | ||
getindex(t::NamedTuple, i::Symbol) = getfield(t, i) | ||
indexed_next(t::NamedTuple, i::Int, state) = (getfield(t, i), i+1) | ||
|
||
convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names,T}) where {names,T} = nt | ||
convert(::Type{NamedTuple{names}}, nt::NamedTuple{names}) where {names} = nt | ||
|
||
function convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names}) where {names,T} | ||
NamedTuple{names,T}(T(nt)) | ||
end | ||
|
||
function show(io::IO, t::NamedTuple) | ||
n = nfields(t) | ||
for i = 1:n | ||
# if field types aren't concrete, show full type | ||
if typeof(getfield(t, i)) !== fieldtype(typeof(t), i) | ||
show(io, typeof(t)) | ||
print(io, "(") | ||
show(io, Tuple(t)) | ||
print(io, ")") | ||
return | ||
end | ||
end | ||
if n == 0 | ||
print(io, "NamedTuple()") | ||
else | ||
print(io, "(") | ||
for i = 1:n | ||
print(io, fieldname(typeof(t),i), " = "); show(io, getfield(t, i)) | ||
if n == 1 | ||
print(io, ",") | ||
elseif i < n | ||
print(io, ", ") | ||
end | ||
end | ||
print(io, ")") | ||
end | ||
end | ||
|
||
eltype(::Type{NamedTuple{names,T}}) where {names,T} = eltype(T) | ||
|
||
==(a::NamedTuple{n}, b::NamedTuple{n}) where {n} = Tuple(a) == Tuple(b) | ||
==(a::NamedTuple, b::NamedTuple) = false | ||
|
||
isequal(a::NamedTuple{n}, b::NamedTuple{n}) where {n} = isequal(Tuple(a), Tuple(b)) | ||
isequal(a::NamedTuple, b::NamedTuple) = false | ||
|
||
_nt_names(::NamedTuple{names}) where {names} = names | ||
_nt_names(::Type{T}) where {names,T<:NamedTuple{names}} = names | ||
|
||
hash(x::NamedTuple, h::UInt) = xor(object_id(_nt_names(x)), hash(Tuple(x), h)) | ||
|
||
isless(a::NamedTuple{n}, b::NamedTuple{n}) where {n} = isless(Tuple(a), Tuple(b)) | ||
# TODO: case where one argument's names are a prefix of the other's | ||
|
||
same_names(::NamedTuple{names}...) where {names} = true | ||
same_names(::NamedTuple...) = false | ||
|
||
function map(f, nt::NamedTuple{names}, nts::NamedTuple...) where names | ||
if !same_names(nt, nts...) | ||
throw(ArgumentError("Named tuple names do not match.")) | ||
end | ||
# this method makes sure we don't define a map(f) method | ||
NT = NamedTuple{names} | ||
if @generated | ||
N = length(names) | ||
M = length(nts) | ||
args = Expr[:(f($(Expr[:(getfield(nt, $j)), (:(getfield(nts[$i], $j)) for i = 1:M)...]...))) for j = 1:N] | ||
:( NT(($(args...),)) ) | ||
else | ||
NT(map(f, map(Tuple, (nt, nts...))...)) | ||
end | ||
end | ||
|
||
# a version of `in` for the older world these generated functions run in | ||
@pure function sym_in(x::Symbol, itr::Tuple{Vararg{Symbol}}) | ||
for y in itr | ||
y === x && return true | ||
end | ||
return false | ||
end | ||
|
||
@pure function merge_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) | ||
names = Symbol[an...] | ||
for n in bn | ||
if !sym_in(n, an) | ||
push!(names, n) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not preserving of the order of the values. Need to switch names = Symbol[]
for n in an
if !sym_in(n, bn)
push!(names, n)
end
end
append!(names, bn)
return names There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think either behavior could be useful but I went with how |
||
end | ||
end | ||
(names...,) | ||
end | ||
|
||
@pure function merge_types(names::Tuple{Vararg{Symbol}}, a::Type{<:NamedTuple}, b::Type{<:NamedTuple}) | ||
bn = _nt_names(b) | ||
Tuple{Any[ fieldtype(sym_in(n, bn) ? b : a, n) for n in names ]...} | ||
end | ||
|
||
""" | ||
merge(a::NamedTuple, b::NamedTuple) | ||
|
||
Construct a new named tuple by merging two existing ones. | ||
The order of fields in `a` is preserved, but values are taken from matching | ||
fields in `b`. Fields present only in `b` are appended at the end. | ||
|
||
```jldoctest | ||
julia> merge((a=1, b=2, c=3), (b=4, d=5)) | ||
(a = 1, b = 4, c = 3, d = 5) | ||
``` | ||
""" | ||
function merge(a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This still bothers me that Additionally, these methods should be written to preserve the input types (such that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree re: element types.
|
||
if @generated | ||
names = merge_names(an, bn) | ||
types = merge_types(names, a, b) | ||
vals = Any[ :(getfield($(sym_in(n, bn) ? :b : :a), $(QuoteNode(n)))) for n in names ] | ||
:( NamedTuple{$names,$types}(($(vals...),)) ) | ||
else | ||
names = merge_names(an, bn) | ||
types = merge_types(names, typeof(a), typeof(b)) | ||
NamedTuple{names,types}(map(n->getfield(sym_in(n, bn) ? b : a, n), names)) | ||
end | ||
end | ||
|
||
merge(a::NamedTuple{()}, b::NamedTuple) = b | ||
|
||
""" | ||
merge(a::NamedTuple, iterable) | ||
|
||
Interpret an iterable of key-value pairs as a named tuple, and perform a merge. | ||
|
||
```jldoctest | ||
julia> merge((a=1, b=2, c=3), [:b=>4, :d=>5]) | ||
(a = 1, b = 4, c = 3, d = 5) | ||
``` | ||
""" | ||
function merge(a::NamedTuple, itr) | ||
names = Symbol[] | ||
vals = Any[] | ||
inds = ObjectIdDict() | ||
for (k,v) in itr | ||
oldind = get(inds, k, 0) | ||
if oldind > 0 | ||
vals[oldind] = v | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here |
||
else | ||
push!(names, k) | ||
push!(vals, v) | ||
inds[k] = length(names) | ||
end | ||
end | ||
merge(a, NamedTuple{(names...,)}((vals...,))) | ||
end | ||
|
||
keys(nt::NamedTuple{names}) where {names} = names | ||
values(nt::NamedTuple) = Tuple(nt) | ||
haskey(nt::NamedTuple, key::Union{Integer, Symbol}) = isdefined(nt, key) | ||
get(nt::NamedTuple, key::Union{Integer, Symbol}, default) = haskey(nt, key) ? getfield(nt, key) : default | ||
get(f::Callable, nt::NamedTuple, key::Union{Integer, Symbol}) = haskey(nt, key) ? getfield(nt, key) : f() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this just be
T
? Otherwise, it just always returnsAny
, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wait, nvm, this is correct for iteration protocol.