Skip to content

Commit

Permalink
use named tuples as keyword varargs. fixes #4916 (#24795)
Browse files Browse the repository at this point in the history
also fixes #9972
  • Loading branch information
JeffBezanson committed Dec 3, 2017
1 parent c70297e commit 04ae93a
Show file tree
Hide file tree
Showing 22 changed files with 212 additions and 237 deletions.
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ 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)`. `kw` can no longer contain multiple entries for the same
argument name ([#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 @@ -462,4 +460,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}(uninitialized, 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}(uninitialized, 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
2 changes: 1 addition & 1 deletion base/distributed/cluster.jl
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ function addprocs(manager::ClusterManager; kwargs...)
end

function addprocs_locked(manager::ClusterManager; kwargs...)
params = merge(default_addprocs_params(), AnyDict(kwargs))
params = merge(default_addprocs_params(), AnyDict(pairs(kwargs)))
topology(Symbol(params[:topology]))

if PGRP.topology != :all_to_all
Expand Down
4 changes: 2 additions & 2 deletions base/distributed/managers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ end

function check_addprocs_args(kwargs)
valid_kw_names = collect(keys(default_addprocs_params()))
for keyname in kwargs
!(keyname[1] in valid_kw_names) && throw(ArgumentError("Invalid keyword argument $(keyname[1])"))
for keyname in keys(kwargs)
!(keyname in valid_kw_names) && throw(ArgumentError("Invalid keyword argument $(keyname)"))
end
end

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}(uninitialized, 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}()
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/interactiveutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ function gen_call_with_extracted_types(__module__, fcn, ex0)
return quote
local arg1 = $(esc(args[1]))
$(fcn)(Core.kwfunc(arg1),
Tuple{Vector{Any}, Core.Typeof(arg1),
Tuple{NamedTuple, Core.Typeof(arg1),
$(typesof)($(map(esc, args[2:end])...)).parameters...})
end
elseif ex0.head == :call
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
11 changes: 5 additions & 6 deletions base/replutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,13 @@ function showerror(io::IO, ex::MethodError)
ft = typeof(f)
name = ft.name.mt.name
f_is_function = false
kwargs = Any[]
kwargs = NamedTuple()
if startswith(string(ft.name.name), "#kw#")
f = ex.args[2]
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 04ae93a

Please sign in to comment.