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

RFC: NamedTuple macro for easier type construction #34548

Merged
merged 7 commits into from
Feb 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ New library functions
New library features
--------------------
* Function composition now works also on one argument `∘(f) = f` (#34251)
* `@NamedTuple{key1::Type1, ...}` macro for convenient `NamedTuple` declarations ([#34548]).


Standard library changes
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,7 @@ export
@s_str, # regex substitution string
@v_str, # version number
@raw_str, # raw string with no interpolation/unescaping
@NamedTuple,

# documentation
@text_str,
Expand Down
37 changes: 37 additions & 0 deletions base/namedtuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ using [`values`](@ref).
Iteration over `NamedTuple`s produces the *values* without the names. (See example
below.) To iterate over the name-value pairs, use the [`pairs`](@ref) function.

The [`@NamedTuple`](@ref) macro can be used for conveniently declaring `NamedTuple` types.

# Examples
```jldoctest
julia> x = (a=1, b=2)
Expand Down Expand Up @@ -324,3 +326,38 @@ julia> Base.setindex(nt, "a", :a)
function setindex(nt::NamedTuple, v, idx::Symbol)
merge(nt, (; idx => v))
end

"""
@NamedTuple{key1::Type1, key2::Type2, ...}
@NamedTuple begin key1::Type1; key2::Type2; ...; end

This macro gives a more convenient syntax for declaring `NamedTuple` types. It returns a `NamedTuple`
type with the given keys and types, equivalent to `NamedTuple{(:key1, :key2, ...), Tuple{Type1,Type2,...}}`.
If the `::Type` declaration is omitted, it is taken to be `Any`. The `begin ... end` form allows the
declarations to be split across multiple lines (similar to a `struct` declaration), but is otherwise
equivalent.

For example, the tuple `(a=3.1, b="hello")` has a type `NamedTuple{(:a, :b),Tuple{Float64,String}}`, which
can also be declared via `@NamedTuple` as:

```jldoctest
julia> @NamedTuple{a::Float64, b::String}
NamedTuple{(:a, :b),Tuple{Float64,String}}

julia> @NamedTuple begin
a::Float64
b::String
end
NamedTuple{(:a, :b),Tuple{Float64,String}}
```
"""
macro NamedTuple(ex)
Meta.isexpr(ex, :braces) || Meta.isexpr(ex, :block) ||
throw(ArgumentError("@NamedTuple expects {...} or begin...end"))
decls = filter(e -> !(e isa LineNumberNode), ex.args)
all(e -> e isa Symbol || Meta.isexpr(e, :(::)), decls) ||
throw(ArgumentError("@NamedTuple must contain a sequence of name or name::type expressions"))
vars = [QuoteNode(e isa Symbol ? e : e.args[1]) for e in decls]
types = [esc(e isa Symbol ? :Any : e.args[2]) for e in decls]
return :(NamedTuple{($(vars...),), Tuple{$(types...)}})
end
1 change: 1 addition & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ Union{}
Core.UnionAll
Core.Tuple
Core.NamedTuple
Base.@NamedTuple
Base.Val
Core.Vararg
Core.Nothing
Expand Down
16 changes: 15 additions & 1 deletion doc/src/manual/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -923,12 +923,26 @@ julia> typeof((a=1,b="hello"))
NamedTuple{(:a, :b),Tuple{Int64,String}}
```

The [`@NamedTuple`](@ref) macro provides a more convenient `struct`-like syntax for declaring
`NamedTuple` types via `key::Type` declarations, where an omitted `::Type` corresponds to `::Any`.

```jldoctest
julia> @NamedTuple{a::Int, b::String}
NamedTuple{(:a, :b),Tuple{Int64,String}}

julia> @NamedTuple begin
a::Int
b::String
end
NamedTuple{(:a, :b),Tuple{Int64,String}}
```

A `NamedTuple` type can be used as a constructor, accepting a single tuple argument.
The constructed `NamedTuple` type can be either a concrete type, with both parameters specified,
or a type that specifies only field names:

```jldoctest
julia> NamedTuple{(:a, :b),Tuple{Float32, String}}((1,""))
julia> @NamedTuple{a::Float32,b::String}((1,""))
(a = 1.0f0, b = "")

julia> NamedTuple{(:a, :b)}((1,""))
Expand Down
12 changes: 12 additions & 0 deletions test/namedtuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,15 @@ let nt0 = NamedTuple(), nt1 = (a=33,), nt2 = (a=0, b=:v)
@test Base.setindex(nt1, "value", :a) == (a="value",)
@test Base.setindex(nt1, "value", :a) isa NamedTuple{(:a,),<:Tuple{AbstractString}}
end

# @NamedTuple
@testset "@NamedTuple" begin
@test @NamedTuple{a::Int, b::String} === NamedTuple{(:a, :b),Tuple{Int,String}} ===
@NamedTuple begin
a::Int
b::String
end
@test @NamedTuple{a::Int, b} === NamedTuple{(:a, :b),Tuple{Int,Any}}
@test_throws LoadError include_string(Main, "@NamedTuple{a::Int, b, 3}")
@test_throws LoadError include_string(Main, "@NamedTuple(a::Int, b)")
end