Skip to content

Commit

Permalink
use named tuples as keyword varargs. fixes #4916
Browse files Browse the repository at this point in the history
also fixes #9972
  • Loading branch information
JeffBezanson committed Nov 26, 2017
1 parent 80d8719 commit 34ed00a
Show file tree
Hide file tree
Showing 17 changed files with 204 additions and 233 deletions.
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ New language features
* Named tuples, with the syntax `(a=1, b=2)`. These behave very similarly to tuples,
except components can also be accessed by name using dot syntax `t.a` ([#22194]).

* Keyword argument containers (`kw` in `f(; kw...)`) are now named tuples. Dictionary
functions like `haskey` and indexing can be used on them, and name-value pairs can be
iterated using `pairs(kw)` ([#4916]).

* Custom infix operators can now be defined by appending Unicode
combining marks, primes, and sub/superscripts to other operators.
For example, `+̂ₐ″` is parsed as an infix operator with the same
Expand Down
40 changes: 38 additions & 2 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,6 @@ export
# constants
nothing, Main

const AnyVector = Array{Any,1}

abstract type Number end
abstract type Real <: Number end
abstract type AbstractFloat <: Real end
Expand Down Expand Up @@ -478,4 +476,42 @@ function (g::GeneratedFunctionStub)(@nospecialize args...)
end
end

NamedTuple() = NamedTuple{(),Tuple{}}(())

"""
NamedTuple{names}(args::Tuple)
Construct a named tuple with the given `names` (a tuple of Symbols) from a tuple of values.
"""
NamedTuple{names}(args::Tuple) where {names} = NamedTuple{names,typeof(args)}(args)

using .Intrinsics: sle_int, add_int

macro generated()
return Expr(:generated)
end

function NamedTuple{names,T}(args::T) where {names, T <: Tuple}
if @generated
N = nfields(names)
flds = Array{Any,1}(N)
i = 1
while sle_int(i, N)
arrayset(false, flds, :(getfield(args, $i)), i)
i = add_int(i, 1)
end
Expr(:new, :(NamedTuple{names,T}), flds...)
else
N = nfields(names)
NT = NamedTuple{names,T}
flds = Array{Any,1}(N)
i = 1
while sle_int(i, N)
arrayset(false, flds, getfield(args, i), i)
i = add_int(i, 1)
end
ccall(:jl_new_structv, Any, (Any, Ptr{Void}, UInt32), NT, fields, N)::NT
end
end

ccall(:jl_set_istopmod, Void, (Any, Bool), Core, true)
1 change: 1 addition & 0 deletions base/coreimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ include("reduce.jl")
include("bitarray.jl")
include("bitset.jl")
include("associative.jl")
include("namedtuple.jl")

# core docsystem
include("docs/core.jl")
Expand Down
6 changes: 3 additions & 3 deletions base/distributed/messages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,17 @@ null_id(id) = id == RRID(0, 0)
struct CallMsg{Mode} <: AbstractMsg
f::Function
args::Tuple
kwargs::Array
kwargs
end
struct CallWaitMsg <: AbstractMsg
f::Function
args::Tuple
kwargs::Array
kwargs
end
struct RemoteDoMsg <: AbstractMsg
f::Function
args::Tuple
kwargs::Array
kwargs
end
struct ResultMsg <: AbstractMsg
value::Any
Expand Down
20 changes: 0 additions & 20 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -634,26 +634,6 @@ function vector_any(@nospecialize xs...)
a
end

function as_kwargs(xs::Union{AbstractArray,Associative})
n = length(xs)
to = Vector{Any}(n*2)
i = 1
for (k, v) in xs
to[i] = k::Symbol
to[i+1] = v
i += 2
end
return to
end

function as_kwargs(xs)
to = Vector{Any}(0)
for (k, v) in xs
ccall(:jl_array_ptr_1d_push2, Void, (Any, Any, Any), to, k::Symbol, v)
end
return to
end

"""
invokelatest(f, args...; kwargs...)
Expand Down
2 changes: 1 addition & 1 deletion base/methodshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function arg_decl_parts(m::Method)
end

function kwarg_decl(m::Method, kwtype::DataType)
sig = rewrap_unionall(Tuple{kwtype, Core.AnyVector, unwrap_unionall(m.sig).parameters...}, m.sig)
sig = rewrap_unionall(Tuple{kwtype, NamedTuple, unwrap_unionall(m.sig).parameters...}, m.sig)
kwli = ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), kwtype.name.mt, sig, typemax(UInt))
if kwli !== nothing
kwli = kwli::Method
Expand Down
45 changes: 34 additions & 11 deletions base/namedtuple.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

if module_name(@__MODULE__) === :Base

"""
NamedTuple{names,T}(args::Tuple)
Expand All @@ -24,16 +26,6 @@ function NamedTuple{names,T}(args::Tuple) where {names, T <: Tuple}
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)
Expand All @@ -50,7 +42,7 @@ function NamedTuple{names}(nt::NamedTuple) where {names}
end
end

NamedTuple() = NamedTuple{(),Tuple{}}(())
end # if Base

length(t::NamedTuple) = nfields(t)
start(t::NamedTuple) = 1
Expand All @@ -60,6 +52,8 @@ 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)
isempty(::NamedTuple{()}) = true
isempty(::NamedTuple) = false

convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names,T}) where {names,T} = nt
convert(::Type{NamedTuple{names}}, nt::NamedTuple{names}) where {names} = nt
Expand Down Expand Up @@ -213,3 +207,32 @@ 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()

@pure function diff_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}})
names = Symbol[]
for n in an
if !sym_in(n, bn)
push!(names, n)
end
end
(names...,)
end

"""
structdiff(a::NamedTuple{an}, b::Union{NamedTuple{bn},Type{NamedTuple{bn}}}) where {an,bn}
Construct a copy of named tuple `a`, except with fields that exist in `b` removed.
`b` can be a named tuple, or a type of the form `NamedTuple{field_names}`.
"""
function structdiff(a::NamedTuple{an}, b::Union{NamedTuple{bn}, Type{NamedTuple{bn}}}) where {an, bn}
if @generated
names = diff_names(an, bn)
types = Tuple{Any[ fieldtype(a, n) for n in names ]...}
vals = Any[ :(getfield(a, $(QuoteNode(n)))) for n in names ]
:( NamedTuple{$names,$types}(($(vals...),)) )
else
names = diff_names(an, bn)
types = Tuple{Any[ fieldtype(typeof(a), n) for n in names ]...}
NamedTuple{names,types}(map(n->getfield(a, n), names))
end
end
9 changes: 4 additions & 5 deletions base/replutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,7 @@ function showerror(io::IO, ex::MethodError)
ft = typeof(f)
name = ft.name.mt.name
arg_types_param = arg_types_param[3:end]
temp = ex.args[1]
kwargs = Any[(temp[i*2-1], temp[i*2]) for i in 1:(length(temp) ÷ 2)]
kwargs = ex.args[1]
ex = MethodError(f, ex.args[3:end])
end
if f == Base.convert && length(arg_types_param) == 2 && !is_arg_types
Expand Down Expand Up @@ -362,7 +361,7 @@ function showerror(io::IO, ex::MethodError)
end
if !isempty(kwargs)
print(io, "; ")
for (i, (k, v)) in enumerate(kwargs)
for (i, (k, v)) in enumerate(pairs(kwargs))
print(io, k, "=")
show(IOContext(io, :limit => true), v)
i == length(kwargs) || print(io, ", ")
Expand Down Expand Up @@ -452,7 +451,7 @@ function showerror_nostdio(err, msg::AbstractString)
ccall(:jl_printf, Cint, (Ptr{Void},Cstring), stderr_stream, "\n")
end

function show_method_candidates(io::IO, ex::MethodError, kwargs::Vector=Any[])
function show_method_candidates(io::IO, ex::MethodError, kwargs::NamedTuple = NamedTuple())
is_arg_types = isa(ex.args, DataType)
arg_types = is_arg_types ? ex.args : typesof(ex.args...)
arg_types_param = Any[arg_types.parameters...]
Expand Down Expand Up @@ -582,7 +581,7 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs::Vector=Any[])
if !isempty(kwargs)
unexpected = Symbol[]
if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords))
for (k, v) in kwargs
for (k, v) in pairs(kwargs)
if !(k in kwords)
push!(unexpected, k)
end
Expand Down
14 changes: 6 additions & 8 deletions doc/src/manual/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -517,14 +517,12 @@ function f(x; y=0, kwargs...)
end
```

Inside `f`, `kwargs` will be a collection of `(key,value)` tuples, where each `key` is a symbol.
Such collections can be passed as keyword arguments using a semicolon in a call, e.g. `f(x, z=1; kwargs...)`.
Dictionaries can also be used for this purpose.

One can also pass `(key,value)` tuples, or any iterable expression (such as a `=>` pair) that
can be assigned to such a tuple, explicitly after a semicolon. For example, `plot(x, y; (:width,2))`
and `plot(x, y; :width => 2)` are equivalent to `plot(x, y, width=2)`. This is useful in situations
where the keyword name is computed at runtime.
Inside `f`, `kwargs` will be a named tuple. Named tuples (as well as dictionaries) can be passed as
keyword arguments using a semicolon in a call, e.g. `f(x, z=1; kwargs...)`.

One can also pass `key => value` expressions after a semicolon. For example, `plot(x, y; :width => 2)`
is equivalent to `plot(x, y, width=2)`. This is useful in situations where the keyword name is computed
at runtime.

The nature of keyword arguments makes it possible to specify the same argument more than once.
For example, in the call `plot(x, y; options..., width=2)` it is possible that the `options` structure
Expand Down
9 changes: 0 additions & 9 deletions src/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -1139,15 +1139,6 @@ JL_DLLEXPORT void jl_array_ptr_1d_append(jl_array_t *a, jl_array_t *a2)
}
}

JL_DLLEXPORT void jl_array_ptr_1d_push2(jl_array_t *a, jl_value_t *b, jl_value_t *c)
{
assert(jl_typeis(a, jl_array_any_type));
jl_array_grow_end(a, 2);
size_t n = jl_array_nrows(a);
jl_array_ptr_set(a, n - 2, b);
jl_array_ptr_set(a, n - 1, c);
}

JL_DLLEXPORT jl_value_t *(jl_array_data_owner)(jl_array_t *a)
{
return jl_array_data_owner(a);
Expand Down
4 changes: 2 additions & 2 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -962,12 +962,12 @@ JL_CALLABLE(jl_f_invoke_kwsorter)
jl_nfields(argtypes));
}
if (jl_is_tuple_type(argtypes)) {
// construct a tuple type for invoking a keyword sorter by putting `Vector{Any}`
// construct a tuple type for invoking a keyword sorter by putting the kw container type
// and the type of the function at the front.
size_t i, nt = jl_nparams(argtypes) + 2;
if (nt < jl_page_size/sizeof(jl_value_t*)) {
jl_value_t **types = (jl_value_t**)alloca(nt*sizeof(jl_value_t*));
types[0] = jl_array_any_type; types[1] = jl_typeof(func);
types[0] = (jl_value_t*)jl_namedtuple_type; types[1] = jl_typeof(func);
for(i=2; i < nt; i++)
types[i] = jl_tparam(argtypes,i-2);
argtypes = (jl_value_t*)jl_apply_tuple_type_v(types, nt);
Expand Down
Loading

0 comments on commit 34ed00a

Please sign in to comment.