Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 14c9f7b

Browse files
stevengjKristofferC
authored andcommittedApr 11, 2020
NamedTuple macro for easier type construction (#34548)
1 parent a466bd1 commit 14c9f7b

File tree

6 files changed

+67
-1
lines changed

6 files changed

+67
-1
lines changed
 

‎NEWS.md

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ New library functions
6161
New library features
6262
--------------------
6363
* Function composition now works also on one argument `∘(f) = f` (#34251)
64+
* `@NamedTuple{key1::Type1, ...}` macro for convenient `NamedTuple` declarations ([#34548]).
6465

6566
* `isapprox` (or ``) now has a one-argument "curried" method `isapprox(x)` which returns a function, like `isequal` (or `==`)` ([#32305]).
6667
* `Ref{NTuple{N,T}}` can be passed to `Ptr{T}`/`Ref{T}` `ccall` signatures ([#34199])

‎base/exports.jl

+1
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,7 @@ export
944944
@s_str, # regex substitution string
945945
@v_str, # version number
946946
@raw_str, # raw string with no interpolation/unescaping
947+
@NamedTuple,
947948

948949
# documentation
949950
@text_str,

‎base/namedtuple.jl

+37
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ using [`values`](@ref).
1717
Iteration over `NamedTuple`s produces the *values* without the names. (See example
1818
below.) To iterate over the name-value pairs, use the [`pairs`](@ref) function.
1919
20+
The [`@NamedTuple`](@ref) macro can be used for conveniently declaring `NamedTuple` types.
21+
2022
# Examples
2123
```jldoctest
2224
julia> x = (a=1, b=2)
@@ -324,3 +326,38 @@ julia> Base.setindex(nt, "a", :a)
324326
function setindex(nt::NamedTuple, v, idx::Symbol)
325327
merge(nt, (; idx => v))
326328
end
329+
330+
"""
331+
@NamedTuple{key1::Type1, key2::Type2, ...}
332+
@NamedTuple begin key1::Type1; key2::Type2; ...; end
333+
334+
This macro gives a more convenient syntax for declaring `NamedTuple` types. It returns a `NamedTuple`
335+
type with the given keys and types, equivalent to `NamedTuple{(:key1, :key2, ...), Tuple{Type1,Type2,...}}`.
336+
If the `::Type` declaration is omitted, it is taken to be `Any`. The `begin ... end` form allows the
337+
declarations to be split across multiple lines (similar to a `struct` declaration), but is otherwise
338+
equivalent.
339+
340+
For example, the tuple `(a=3.1, b="hello")` has a type `NamedTuple{(:a, :b),Tuple{Float64,String}}`, which
341+
can also be declared via `@NamedTuple` as:
342+
343+
```jldoctest
344+
julia> @NamedTuple{a::Float64, b::String}
345+
NamedTuple{(:a, :b),Tuple{Float64,String}}
346+
347+
julia> @NamedTuple begin
348+
a::Float64
349+
b::String
350+
end
351+
NamedTuple{(:a, :b),Tuple{Float64,String}}
352+
```
353+
"""
354+
macro NamedTuple(ex)
355+
Meta.isexpr(ex, :braces) || Meta.isexpr(ex, :block) ||
356+
throw(ArgumentError("@NamedTuple expects {...} or begin...end"))
357+
decls = filter(e -> !(e isa LineNumberNode), ex.args)
358+
all(e -> e isa Symbol || Meta.isexpr(e, :(::)), decls) ||
359+
throw(ArgumentError("@NamedTuple must contain a sequence of name or name::type expressions"))
360+
vars = [QuoteNode(e isa Symbol ? e : e.args[1]) for e in decls]
361+
types = [esc(e isa Symbol ? :Any : e.args[2]) for e in decls]
362+
return :(NamedTuple{($(vars...),), Tuple{$(types...)}})
363+
end

‎doc/src/base/base.md

+1
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ Union{}
208208
Core.UnionAll
209209
Core.Tuple
210210
Core.NamedTuple
211+
Base.@NamedTuple
211212
Base.Val
212213
Core.Vararg
213214
Core.Nothing

‎doc/src/manual/types.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -923,12 +923,26 @@ julia> typeof((a=1,b="hello"))
923923
NamedTuple{(:a, :b),Tuple{Int64,String}}
924924
```
925925

926+
The [`@NamedTuple`](@ref) macro provides a more convenient `struct`-like syntax for declaring
927+
`NamedTuple` types via `key::Type` declarations, where an omitted `::Type` corresponds to `::Any`.
928+
929+
```jldoctest
930+
julia> @NamedTuple{a::Int, b::String}
931+
NamedTuple{(:a, :b),Tuple{Int64,String}}
932+
933+
julia> @NamedTuple begin
934+
a::Int
935+
b::String
936+
end
937+
NamedTuple{(:a, :b),Tuple{Int64,String}}
938+
```
939+
926940
A `NamedTuple` type can be used as a constructor, accepting a single tuple argument.
927941
The constructed `NamedTuple` type can be either a concrete type, with both parameters specified,
928942
or a type that specifies only field names:
929943

930944
```jldoctest
931-
julia> NamedTuple{(:a, :b),Tuple{Float32, String}}((1,""))
945+
julia> @NamedTuple{a::Float32,b::String}((1,""))
932946
(a = 1.0f0, b = "")
933947
934948
julia> NamedTuple{(:a, :b)}((1,""))

‎test/namedtuple.jl

+12
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,15 @@ let nt0 = NamedTuple(), nt1 = (a=33,), nt2 = (a=0, b=:v)
276276
@test Base.setindex(nt1, "value", :a) == (a="value",)
277277
@test Base.setindex(nt1, "value", :a) isa NamedTuple{(:a,),<:Tuple{AbstractString}}
278278
end
279+
280+
# @NamedTuple
281+
@testset "@NamedTuple" begin
282+
@test @NamedTuple{a::Int, b::String} === NamedTuple{(:a, :b),Tuple{Int,String}} ===
283+
@NamedTuple begin
284+
a::Int
285+
b::String
286+
end
287+
@test @NamedTuple{a::Int, b} === NamedTuple{(:a, :b),Tuple{Int,Any}}
288+
@test_throws LoadError include_string(Main, "@NamedTuple{a::Int, b, 3}")
289+
@test_throws LoadError include_string(Main, "@NamedTuple(a::Int, b)")
290+
end

0 commit comments

Comments
 (0)
Please sign in to comment.