Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I retrieve keyword arguments out of a field of splatted kwargs?

Tags:

julia

If I have a function signature like f(args...; kwargs...), how can I get a specific keyword out of kwargs? Naïvely typing kwargs.x does not work:

julia> f(args...; kwargs...) = kwargs.x
f (generic function with 1 method)

julia> f(x=1)
ERROR: type Pairs has no field x
Stacktrace:
 [1] getproperty(::Base.Iterators.Pairs{Symbol,Int64,Tuple{Symbol},NamedTuple{(:x,),Tuple{Int64}}}, ::Symbol) at ./Base.jl:20
 [2] #f#7(::Base.Iterators.Pairs{Symbol,Int64,Tuple{Symbol},NamedTuple{(:x,),Tuple{Int64}}}, ::typeof(f)) at ./REPL[2]:1
 [3] (::var"#kw##f")(::NamedTuple{(:x,),Tuple{Int64}}, ::typeof(f)) at ./none:0
 [4] top-level scope at REPL[3]:1

This question appeared on the JuliaLang Slack channel in the #helpdesk. For an automatic invite to the very helpful julia slack, simply fill out https://slackinvite.julialang.org

like image 943
Mason Avatar asked Dec 12 '19 01:12

Mason


1 Answers

The reason this happens is that splatted keyword arguments are not stored in a named tuple by default. We can see how they're stored like so:

julia> g(;kwargs...) = kwargs
g (generic function with 1 method)

julia> g(a=1)
pairs(::NamedTuple) with 1 entry:
  :a => 1

julia> g(a=1) |> typeof
Base.Iterators.Pairs{Symbol,Int64,Tuple{Symbol},NamedTuple{(:a,),Tuple{Int64}}}

So the splatted kwargs are instead stored as some sort of iterator object. However, we can easily convert that kwargs iterator to a NamedTuple like so: (;kwargs...) and then access it in the way we'd expect, so your example would translate into

julia> f(args...; kwargs...) = (;kwargs...).x
f (generic function with 1 method)

julia> f(x=1, y=2)
1

Of course, the more idiomatic way to do this would be to instead write the function as

julia> f(args...; x, kwargs...) = x
f (generic function with 1 method)

julia> f(x=1, y=2)
1

but this assumes you know the name you want to access (x) at the time when you write the function.


A brief sidenote: If we return to our example of g(;kwargs...) = kwargs, we can ask for the fieldnames of the iterator object the was returned like so:

julia> g(x=1, y=2) |> typeof |> fieldnames
(:data, :itr)

Hm, what is this data field?

julia> g(x=1, y=2).data
(x = 1, y = 2)

Aha! so we can actually get the kwargs as a named tuple using that, i.e. f(;kwargs...) = kwargs.data.x would work, but I wouldn't recommend this approach since it seems to rely on undocumented behaviour, so it may be a mere implementation detail that is not guaranteed to be stable across julia versions.

like image 163
Mason Avatar answered Oct 04 '22 01:10

Mason