Skip to content

Commit

Permalink
NamedTuple macro for easier type construction (#34548)
Browse files Browse the repository at this point in the history
  • Loading branch information
stevengj committed Feb 21, 2020
1 parent c17be46 commit f5dbc47
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 1 deletion.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,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]).

* `isapprox` (or ``) now has a one-argument "curried" method `isapprox(x)` which returns a function, like `isequal` (or `==`)` ([#32305]).
* `Ref{NTuple{N,T}}` can be passed to `Ptr{T}`/`Ref{T}` `ccall` signatures ([#34199])
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,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 @@ -208,6 +208,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

0 comments on commit f5dbc47

Please sign in to comment.