Bagaimana cara mengambil argumen kata kunci dari bidang kwargs splatted?

9

Jika saya memiliki tanda tangan fungsi f(args...; kwargs...), bagaimana saya bisa mendapatkan kata kunci tertentu kwargs? Mengetik secara naif kwargs.xtidak berfungsi:

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

Pertanyaan ini muncul di saluran Slack JuliaLang di #helpdesk. Untuk undangan otomatis ke julia slack yang sangat membantu, cukup isi https://slackinvite.julialang.org

Tukang batu
sumber

Jawaban:

10

Alasan ini terjadi adalah bahwa argumen kata kunci splatted tidak disimpan dalam tuple bernama secara default. Kita bisa melihat bagaimana mereka disimpan seperti ini:

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}}}

Jadi, splatted kwargs malah disimpan sebagai semacam objek iterator. Namun, kami dapat dengan mudah mengonversi kwargsiterator ke NamedTuple seperti: (;kwargs...)dan kemudian mengaksesnya seperti yang kami harapkan, jadi contoh Anda akan diterjemahkan ke dalam

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

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

Tentu saja, cara yang lebih idiomatis untuk melakukan ini adalah menulis fungsi sebagai gantinya

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

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

tetapi ini mengasumsikan Anda tahu nama yang ingin Anda akses ( x) pada saat Anda menulis fungsi.


Sidenote singkat: Jika kita kembali ke contoh g(;kwargs...) = kwargs, kita dapat meminta nama field objek iterator yang dikembalikan seperti:

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

Hm, databidang apa ini ?

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

Aha! jadi kita benar-benar bisa mendapatkan kwarg sebagai tuple bernama menggunakan itu, yaitu f(;kwargs...) = kwargs.data.xakan bekerja, tapi saya tidak akan merekomendasikan pendekatan ini karena tampaknya bergantung pada perilaku tidak berdokumen, jadi itu mungkin hanya detail implementasi belaka yang tidak dijamin stabil di versi julia.

Tukang batu
sumber