-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
NamedTuple unpacking (and also struct unpacking) #28579
Comments
|
That would then look: f((;a, )) = 2a
# or
f(x, y, (;a, b)) = x+y+a+b Could be good. The only potential problem could be that it would be easy to miss the The As a bonus, this would be non-breaking. Edit: fixed unneeded/confusing default-value method-defs. |
No more verbose than the calling syntax and it would allow restructuring the named value to a variable with a different name. It's currently pretty common to see stuff like this: something = ...
f(a, b, c; something=something) If we had a shorthand for the case where the local name matches the keyword name then it would also make sense to use that same syntax for destructuring when the names match. Spitballing, it could be: f(a, b, c, =something)
f(a, b, c, something=) I think I prefer the former. |
It has been proposed before (I believe by @davidanthoff ) to make |
I use when we are free to use some unicode as shorthand x⥍ ≝ |
(with frequency .. about 80GHz today) |
How would it be known what IMO, this feature is orthogonal to kwargs. It makes sense to name-unpack the fields of a positional argument (using the syntax of #28579 (comment)): f(x, y, (;a, b); kw1=1, kw2=y) = ...
f(1,2, (a=3, b=8, u=9)) and also name-unpack a kwarg: g(x, y; kw1=1, (;a, b)=p, kw2=y) = ...
g(1,2, p=(a=7, b=9, u=8)) Thus a syntax working for both should be found. I think above seems reasonable. |
Somewhat related: I am experimenting with a package called EponymTuples.jl, which allows replacing f((a, b)::NamedTuple{(:a, :b), <: Tuple{Any, Int}}) = ...
(a = a, b = b, c = 3) with f(@eponymargs(a, b::Int)) = ...
@eponymtuple(a, b, c = 3) I find it helpful for cases when I don't want to introduce and name a |
I happily noticed recently in the News.md for 1.5 that #34331 was merged, which I find super useful. At this point the language seems perfectly set up to implement the Issue here, with the syntax being that a named tuple on the LHS means unpacking into those names. It gives a great symmetry between packing/unpacking named/unnamed arguments: # pack named or unnamed arguments
foo(x,y)
foo(;x,y)
# unpack named or unnamed arguments
(x,y) = bar()
(;x,y) = bar() If A nice testament to the consistency of all of this is that you would have that both of these are valid and a no-op as you'd expect: # valid but no-op
(x,y) = (x,y)
(;x,y) = (;x,y) Would be great if something like this could be considered. |
Agreed. I'm particularly interested in the destructuring of structs and named tuples in function arguments. If we had that feature, then we would have ~70% of the pattern matching capabilities of ML languages. The only parts we would be missing are
|
Someone show me exactly what should happen given what? |
Good question. The original post seems to focus on unpacking in function arguments, which is the part I'm most interested in. So, the syntax might look like the following: Named tuple unpacking in positional function arguments# define
foo(q, (; x, z)) = q + x + z
# call
foo(1, (x=2, y=3, z=4)) # returns 7 Struct unpacking in positional function argumentsI think struct unpacking in function arguments requires a separate syntax. Perhaps something like this: struct A
x
y
z
end
# define
bar(q, A x z) = q + x + z
# call
a = A(2, 3, 4)
bar(1, a) # returns 7 |
@JeffreySarnoff, the idea is like this: struct T
x::Int
y::Float64
end
t = T(10, 3.14)
nt = (a=1, b=2, c=3)
(;x, y) = t
(;a, b, c) = nt so the Javascript has this (in recent versions). |
thx |
@CameronBieganek just want to highlight that
doesn't need to be the case. Since Julia already lowers argument unpacking from foo((x,y),) = ... to something like foo(tmp) = ((x,y) = tmp; ...) then it would be natural that |
@marius311 That's interesting, but I would want foo(q, (; x, z)) and foo(q, A x z) to be two separate methods in the method table for However, I see now that the current tuple unpacking in positional arguments does not create a julia> foo((x, y)) = x + y
foo (generic function with 1 method)
julia> methods(foo)
# 1 method for generic function "foo":
[1] foo(::Any) in Main at REPL[12]:1 |
You could imagine being able to do, foo(q, (; x, z) :: A) = ... which would be totally consistent with how you can currently do julia> foo((x, y)::Tuple{Any,Any}) = x + y
foo (generic function with 1 method)
julia> methods(foo)
# 1 method for generic function "foo":
[1] foo(::Tuple{Any,Any}) in Main at REPL[1]:1 |
Phew, I'm glad that exists as a workaround, but it still seems wrong to me that In other words, julia> foo((x, y)) = x + y
foo (generic function with 1 method)
julia> foo(3)
ERROR: BoundsError: attempt to access Int64
at index [2]
Stacktrace:
[1] indexed_iterate(::Int64, ::Int64, ::Nothing) at ./tuple.jl:90
[2] foo(::Int64) at ./REPL[1]:1
[3] top-level scope at REPL[2]:1 |
I'm guessing the reason for this is because |
Would |
Bumping this old issue. Given that we have automatic keyword assignment as in args = [(;x, y) for x in 1:4, y in 5:6]
map(args) do (;x, y)
x + y
end (I'm here because I had a bug in a more complicated version of the above, due to unpacking arguments in the incorrect order; this could have been avoided if the proposed syntax was available). |
This is how it's done in JavaScript: const nt = { a: 1, b: 2 }
const f = ({ b }) => b
f(nt)
Also: const c = 3
const d = 4
{ c, d }
|
Worth adding, this also works for variable assignment (not in a function argument): const nt = {a: 1, b:2}
const {b, a} = nt // a = 1; b = 2 |
This currently only allows the form `(; a, b) = x` and lowers this to calls to `getproperty`. We could think about whether we want to allow specifying default values for properties as well, or even some kind of property renaming in the lhs. We could even allow slurping unused properties, but all of that sounds more difficult to work out in detail and potentially controversial, so I left this as an error for now. fixes #28579
Yes, I'd like to reiterate that I'm particularly interested in being able to destructure (pattern match) a named tuple or a struct in a function argument, as was requested by OP. When you combine multiple dispatch with struct destructuring in function arguments, you get pretty close to the pattern matching capabilities of a language like Haskell. |
It's also possible to destructure deeper objects and rename parameters. const T = {
a: 1,
b: 2,
c: { d: 3, e: 4 },
};
const f = ({ b: paramB, c: { d: paramD, e } }) => paramB * paramD * e;
console.log(f(T)); // 24 |
#39285 currently only implements the more minimal version of this proposal, but we could think about allowing renaming as well, so this example could be written as: f((; b=paramB, c=(; d=paramD, e))) = paramB * paramD * e This should even compose nicely when nested with regular destructuring for iterators, so this would already give us quite powerful pattern matching capabilities. |
I think it would make more sense to put the new name on the LHS of f((; paramB=b, c=(; paramD=d, e))) = paramB * paramD * e |
Shouldn't this be f((; paramB=b, (; paramD=d, e)=c)) = paramB * paramD * e then instead? I initially thought of it that way as well, but it just seems very weird to have anything other than symbols as keys for kwarg syntax. I also think the other way makes a lot more sense, if you think about it in terms of pattern matching. Perhaps this confusion is indicative that we maybe should hold off on the more complicated cases for now though. |
Yes I had the c in the wrong place, good catch. I did type that out on my phone though so I wouldn't take this error as evidence against the syntax. I think if we were going to use the order, I initially thought renaming was unnecessary, but I then realized that without it, you wouldn't be able to apply the approach to e.g. a pair of namedtuples of the same type, as in f((;a=>a1, b=>b1), (;a=>a2, b=>b2)) = (a=a1 + a2, b=b1-b2)
f((;a1=a, b1=b), (;a2=a, b2=b)) = (a=a1 + a2, b=b1-b2) I imagine many cases where one would want to do this. So I think it is worth further consideration. On the other hand, if you think we could get this into 1.7, I would be all in favor of pushing forward with the fantastic improvement you've already implemented. |
We can't really use julia> (a => b,) = [42]
1-element Array{Int64,1}:
42
julia> 1 => 2
42 |
What is this madness??! Can you point me to documentation that would help me understand this? I am completely at a loss. |
It's the same as |
Ahhhh, okay got it. Thanks! |
This currently only allows the form `(; a, b) = x` and lowers this to calls to `getproperty`. We could think about whether we want to allow specifying default values for properties as well, or even some kind of property renaming in the lhs. We could even allow slurping unused properties, but all of that sounds more difficult to work out in detail and potentially controversial, so I left this as an error for now. fixes #28579
* implement property destructuring This currently only allows the form `(; a, b) = x` and lowers this to calls to `getproperty`. We could think about whether we want to allow specifying default values for properties as well, or even some kind of property renaming in the lhs. We could even allow slurping unused properties, but all of that sounds more difficult to work out in detail and potentially controversial, so I left this as an error for now. fixes #28579 * add NEWS entry
* implement property destructuring This currently only allows the form `(; a, b) = x` and lowers this to calls to `getproperty`. We could think about whether we want to allow specifying default values for properties as well, or even some kind of property renaming in the lhs. We could even allow slurping unused properties, but all of that sounds more difficult to work out in detail and potentially controversial, so I left this as an error for now. fixes JuliaLang#28579 * add NEWS entry
* implement property destructuring This currently only allows the form `(; a, b) = x` and lowers this to calls to `getproperty`. We could think about whether we want to allow specifying default values for properties as well, or even some kind of property renaming in the lhs. We could even allow slurping unused properties, but all of that sounds more difficult to work out in detail and potentially controversial, so I left this as an error for now. fixes JuliaLang#28579 * add NEWS entry
During JuliaCon discussion with @ahojukka5, I figured it would be cool to have NamedTuple unpacking in function arguments which honors the names of the named tuple. Currently we have:
instead, I'd think this would be more intuitive:
Similarly it could work for structs:
(Unfortunately, this needs a 2.0 label)
Edit: fixed unneeded/confusing default-value method-defs.
The text was updated successfully, but these errors were encountered: