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

use named tuples as keyword varargs. fixes #4916 #24795

Merged
merged 1 commit into from
Dec 3, 2017
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
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