Make function arguments positional or keyword


#1

How do I create a function that can take either positional or keyword arguments? Right now, I have a function that takes keyword arguments. But, I would to revise it to take either positional or keyword arguments. Thanks.


#2

You can overload this function, e.g.:

julia> foo(x) = println(x)
foo (generic function with 2 methods)

julia> foo(;x=1) = foo(x)
foo (generic function with 2 methods)

julia> foo(42)
42

julia> foo(x=42)
42

#3

Is it possible to do that without passing default values in optional arguments? The function I have is for a stats model and I do not want to pass default values.

@ChrisRackauckas


#4

Julia does dispatching on default values. So what you can do is:

function f(x,y=nothing;z=:SomeOption)
  if y == nothing
    return 5
  else # y is some number
    if z == :SomeOption
      return x+2y
    else
      return x+3y
    end
  end
end

Via dispatch, it will compile a different function when y is a number, so you don’t need to worry about the internal checks. To see this, notice that when y is not given, all of the other logic compiles away:

julia> @code_llvm f(2)

; Function Attrs: uwtable
define i64 @julia_f_68264(i64) #0 !dbg !5 {
top:
  ret i64 5
}

and thisis just a function which returns 5. In the other case:

julia> @code_llvm f(2,3)

; Function Attrs: uwtable
define i64 @julia_f_68300(i64, i64) #0 !dbg !5 {
top:
  %2 = call i8**** @jl_get_ptls_states() #4
  %3 = alloca [7 x i8**], align 8
  %.sub = getelementptr inbounds [7 x i8**], [7 x i8**]* %3, i64 0, i64 0
  %4 = getelementptr [7 x i8**], [7 x i8**]* %3, i64 0, i64 2
  %5 = bitcast i8*** %4 to i8*
  call void @llvm.memset.p0i8.i32(i8* %5, i8 0, i32 40, i32 8, i1 false)
  %6 = bitcast [7 x i8**]* %3 to i64*
  store i64 10, i64* %6, align 8
  %7 = getelementptr [7 x i8**], [7 x i8**]* %3, i64 0, i64 1
  %8 = bitcast i8**** %2 to i64*
  %9 = load i64, i64* %8, align 8
  %10 = bitcast i8*** %7 to i64*
  store i64 %9, i64* %10, align 8
  store i8*** %.sub, i8**** %2, align 8
  %11 = getelementptr [7 x i8**], [7 x i8**]* %3, i64 0, i64 6
  %12 = getelementptr [7 x i8**], [7 x i8**]* %3, i64 0, i64 5
  %13 = getelementptr [7 x i8**], [7 x i8**]* %3, i64 0, i64 4
  %14 = getelementptr [7 x i8**], [7 x i8**]* %3, i64 0, i64 3
  store i8** inttoptr (i64 2147434592 to i8**), i8*** %4, align 8
  store i8** inttoptr (i64 71900760 to i8**), i8*** %14, align 8
  store i8** inttoptr (i64 2147434584 to i8**), i8*** %13, align 8
  %15 = call i8** @jl_box_int64(i64 signext %0)
  store i8** %15, i8*** %12, align 8
  %16 = call i8** @jl_box_int64(i64 signext %1)
  store i8** %16, i8*** %11, align 8
  %17 = call i8** @jl_invoke(i8** inttoptr (i64 2271350544 to i8**), i8*** %4, i32 5)
  %18 = bitcast i8** %17 to i64*
  %19 = load i64, i64* %18, align 16
  %20 = load i64, i64* %10, align 8
  store i64 %20, i64* %8, align 8
  ret i64 %19
}

it compiles to a function which has to do a check on symbols and branch etc.

A better way, sometimes, is to use dispatch:

f(x;z=:SomeOption) = 5
function f(x,y;z=:SomeOption)
   if z == :SomeOption
     return x+2y
   else
     return x+3y
   end
end

this is equivalent to the code above but may organize your code better in some cases. You can think of this as the “no default values” case.

A very common way to use this is, if you have a lot of code that you want to share, have an internal function named _f that does the big calculations. Then you have your different dispatches do setup and call _f. This organizes by dispatch but still re-uses most of the internal code.


#5

@ChrisRackauckas: Thanks. I thought it would be helpful to share part of the function I wrote which shows the parameter types as well. With using someOption works, but I can still declare the parameter types right?
For e.g.
function squared(;x=:SomeOption::Int)

Here is what my fuction looks like

function sAUC(; x::DataFrames.Formula = nothing, treatment_group::Symbol = nothing, data::DataFrames.DataFrame = nothing)
...

I would like to revise this function so it can take either all positional or keyword arguments. Thanks.


#6

function squared(;x::Int=1)

But note that keyword arguments do not do dispatch yet, so this is actually a type assertion instead of normal dispatch. That should change before 1.0 is out:


#7

@ChrisRackauckas: Can we make this function to take :SomeOption in all parameters?
Thanks.


#8

What is the reason for this? This R-coding style is not idiomatic in Julia.


#9

I don’t know what you mean. What are parameters? That’s not a Julian word for this, and we need to use specifics here since the details can be subtle.


#10

@mkborregaard: Thanks. Do you mean that we can only do either positional or keyword arguments, but not both in Julia?


#11

Yes, exactly (or to be exact, each argument should be either a keyword or a positional argument - you can combine both types in the same call). They are very different in Julia, as (at the moment) positional arguments are used to dispatch, keywords aren’t.


#12

@mkborregaard @ChrisRackauckas: I am deleting this response because it is still being tested.

@mkborregaard: you are correct about the following statement. Thanks. Is there any plan to allow both keyword and a positional argument in the future? Thanks a lot.

Yes, exactly (or to be exact, each argument should be either a keyword or a positional argument - you can combine both types in the same call).


#13

I meant to say if we can use :SomeOption for all parameters. It looks like we can. Thanks.

function squared(;x =:SomeOption::Int64, y =:SomeOption::Int64)
  return x+y
end

#14

@mkborregaard @ChrisRackauckas: Here is a workaround to the above problem. Any advice/comments? Thanks a lot.

function tryFunction(; x::Int64=nothing,y::Int64=nothing)
  if x == nothing || y == nothing
    error("Specify arguments")
  end
  tryFunction(x::Int64, y::Int64)
end

function tryFunction(x::Int64,y::Int64)
  return(x + y)
end

tryFunction(x=2,y=7)
tryFunction(2,7)
tryFunction(y=2,x=7)  

#15

Since the argument defaults get evaluated at call time, you can actually have the default itself be an expression that throws an error:

julia> f(;x::Int=throw(ArgumentError("missing x"))) = x
f (generic function with 1 method)

julia> f(x=2)
2

julia> f()
ERROR: ArgumentError: missing x
 in f() at ./REPL[1]:1

#16

@mbauman:
@mkborregaard: Thanks. How do I combine this with the function so it takes positional arguments as well to fit into the tryFunction I wrote above?


#17

@mbauman: I tried to replicate the same function in Atom.But, it did not run. Any idea? Thanks.

f(; x::Int=throw(ArgumentError("missing x")))
  return(x)
end

I got this error message;

syntax: keyword argument is not a symbol: "x::Int" in include_string(::String, ::String) at loading.jl:441 in include_string(::String, ::String, ::Int64) at eval.jl:28 in include_string(::Module, ::String, ::String, ::Int64, ::Vararg{Int64,N}) at eval.jl:32 in (::Atom.##53#56{String,Int64,String})() at eval.jl:40 in withpath(::Atom.##53#56{String,Int64,String}, ::Void) at utils.jl:30 in withpath(::Function, ::String) at eval.jl:46 in macro expansion at eval.jl:57 [inlined] in (::Atom.##52#55{Dict{String,Any}})() at task.jl:60


#18

You’re missing the function (or to do like the example, where the function was defined in a short form using = x).


#19

This tryFunction approach works, of course. There is nothing wrong with it :slight_smile: I was just curious of why you wanted to achieve this design.


#20

@mkborregaard: oh, my bad. So, that works without function in REPL , but in ATOM?? Thanks.