Make function arguments positional or keyword

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.