Calling functions with named tuples by element name

Allowing functions to read named tuple elements by name is a wonderful feature of 1.7. Thank you :slight_smile:
I can now write:

a,b,c,d = 1:4

f( (;a,b) = (a=0,b=0)  ) = a - b

f((;c,d,b,a))    #result -1
f()              #result  0

It would be great to be able to specify default values within the tuple and optionally omit these in the function call

f( (;a,b=0) ) = a - b

f((a=1))                      #result   1
f((b=2))                      #result  error  - no default value for tuple element 'a' specified.

I’m having a hard time understanding what this code does.

1 Like

I can see how that feature would be convenient in the particular case you mentioned, but why not instead use normal keyword arguments:

f(; a, b=0) = a - b

which already works exactly as desired:

julia> f(a=1)
1

julia> f(b=2)
ERROR: UndefKeywordError: keyword argument a not assigned
Stacktrace:
 [1] top-level scope
   @ REPL[3]:1

Note also that if you happen to have a named tuple, you can still pass it as the keyword arguments to a function:

julia> args = (a=1, b=2)
(a = 1, b = 2)

julia> f(; args...)
-1

julia> args = (b = 2,)
(b = 2,)

julia> f(; args...)
ERROR: UndefKeywordError: keyword argument a not assigned
Stacktrace:
 [1] top-level scope
   @ REPL[7]:1 
4 Likes

A potential reason is the lack of dispatch by keyword arguments. Dispatch can be used with destructuring in a kinda-convenient way:

julia> g((;a,b,c)::NamedTuple{(:a,:b,:c)}) = "abc"
g (generic function with 1 method)

julia> g((;a,b)::NamedTuple{(:a,:b)}) = "ab"
g (generic function with 2 methods)
3 Likes

f(; args...) – That’s a good point. Thank you :slight_smile:

What if args also contains an element that isn’t a function variable?
Can it still be used without causing an error?

I have a function that takes a 10 inputs and returns 5 outputs.
I want to run it on each row of a CSV which has 30 columns. The necessary field names match with the keyword arguments so I’d like to just send each row to the function without having to filter it down.
As shown below this gives an error.

function f(; a=0, b=0 ) 
    x = a + b    
    y = a - b
    (; x, y)
end

D1 = DataFrame( a=1:5 )
D2 = DataFrame( a=1:5,  C = 1:5 )

@pipe eachrow(D1) .|>   f(;_...) |> DataFrame      #works   
@pipe eachrow(D2) .|>   f(;_...) |> DataFrame      #Error  - no C element in function parameters

Another option
transform with ByRow is a fantastic function.
but you need to map both the inputs and outputs
If the field names match the function inputs / outputs it would be great you could just tell it to map each row.

transform(D2, :a => ByRow( a-> f(a=a)) => [:x,:y]  )            #works
transform(D2,  ALL_COLUMNS => f => COLUMN_NAMES_FROM_OUTPUT  )  #Nice to have
1 Like

You could just define f as:

function f(; a=0, b=0, _...) 
    x = a + b    
    y = a - b
    (; x, y)
end

which should just ignore any additional fields.

4 Likes

could you point out where is the syntax _... defined?
Thank you.

This is just basic splat-ing. See ? .... The _ is just a variable name.

2 Likes