Forwarding optional function inputs

Hi, is there a way to nicely overload functions with optional inputs that call other functions with optional inputs (if that makes sense).

const DEFAULT_K = 10.0;
const DEFAULT_N = 10.0;

struct A
    x::Float64
end

xtimesk(x::Float64, k::Float64 = DEFAULT_K) = x * k             # (0)
xtimesk(a::A, k::Float64)                   = xtimesk(a.x, k)   # (1)
xtimesk(a::A)                               = xtimesk(a.x)      # (2)

xtimesn(x::Float64, n::Float64 = DEFAULT_N) = x * n             # (3)
xtimesn(a::A, n::Float64 = DEFAULT_N)       = xtimesk(a.x, n)   # (4)

a = A(2.2)

@printf("%g\n%g\n%g\n%g\n", xtimesk(a), xtimesk(a, 5.0), xtimesn(a), xtimesn(a, 5.0))

I’ve put together a small example of what I’m trying to achieve above, with xtimesk() giving me what I want with option to call it with or without k, but is there a way to define it in a single line?

I could do something along the lines of xtimesn() but that means defining the default input value twice which I would also like to prevent.

Is there another way to do this? I’m coming from Matlab and there I would do this as something like this:

classdef A
    properties
        x (1,1) double
    end
    
    methods
        function this = A(inX)
            this.x = inX;
        end

        function out = xtimesk(this, varargin)
            out = xtimesk(this.x, varargin{:});
        end
    end
end

function out = xtimesk(x, k)
    if nargin < 2
        k = 10.0;
    end
    out = x * k;
end

I assume there’s some similar solution like varargin in Julia? But was wondering if there is also a neater way to achieve this without obfuscating the code by use of something like varargin?

Thanks,
Titas

Maybe there’s a better way, but this is what i would do:

julia> valueof(x::Number) = x
valueof (generic function with 1 method)

julia> valueof(a::A) = a.x
valueof (generic function with 2 methods)

julia> xtimesk(x, k::Float64 = DEFAULT_K) = valueof(x) * k
xtimesk (generic function with 2 methods)

julia> xtimesn(x, n::Float64 = DEFAULT_N) = valueof(x) * n
xtimesn (generic function with 2 methods)

julia> a = A(2.2)
A(2.2)

julia> @show xtimesk(a) xtimesk(a, 5.0) xtimesn(a) xtimesn(a, 5.0);
xtimesk(a) = 22.0
xtimesk(a, 5.0) = 11.0
xtimesn(a) = 22.0
xtimesn(a, 5.0) = 11.0

Perhaps it would make sense to define *(a::A, b) = *(a.x, b); xtimesk(x, k=DEFAULTK) = x*k. Or even go into promotion rules if A is a Number candidate.

1 Like

Thanks, good to know, but not exactly what I’m looking for. This seems to be equivalent to rewriting my example as:

xtimesk(x::Float64, k::Float64)         = x * k             # (0)
xtimesk(a::A, k::Float64 = DEFAULT_K)   = xtimesk(a.x, k)   # (1)

And ideally the defaults would be defined at the lowest level - (0) in this case. Multiplication here was just a simple example to explain what I would like to achieve.

Thanks, didn’t even think about doing it this way. This seems to do exactly what I’m looking for, however it adds a bit of indirection and if I write it like this:

struct B
    x::Float64
end

valueof(x::Number) = x
valueof(b::B) = b.x
xtimesk2(x, k::Float64 = DEFAULT_K) = valueof(x) * k
xtimesn2(x, n::Float64 = DEFAULT_N) = valueof(x) * n

b = B(2.2)
@show xtimesk2(b) xtimesk2(b, 5.0) xtimesn2(b) xtimesn2(b, 5.0)

I then lose the abilty to find xtimesk as a method available with A/B:

julia> methodswith(A)
[1] xtimesk(a::A) in Main at ...
[2] xtimesk(a::A, k::Float64) in Main at ...
[3] xtimesn(a::A) in Main at ...
[4] xtimesn(a::A, n::Float64) in Main at ...

julia> methodswith(B)
[1] valueof(b::B) in Main at ...

Why is that ideal? Presumably the default is part of the API, so it seems reasonable to define it in public API methods.
You can mimic your MATLAB example like this:

xtimesk(x::Float64, k::Float64 = DEFAULT_K) = x * k  
xtimesk(a::A, args...) = xtimesk(a.x, args...) 

but I’m not sure it’s better than simply repeating = DEFAULT_K in each method signature (as long as DEFAULT_K itself is only defined once).

1 Like

Thanks, this is the answer I was looking for (although was hoping there was a nicer way in Julia where you don’t lose having named arguments).

Didn’t mean ideal as an ideal way to do something like this, was more meant as “I would like to find a way to do it like this”. Just looking to find ways to do things in Julia that I am used to in Matlab, so simply trying to replicate those and learn the language along the way.

Your example is not exactly the same, because my definition also allows xtimesk(1.0), which I would imagine is the reason you would want the default to be defined “at a lower level”. In general it is considered good Julia to add as few type annotations as possible.