Best way to have an optional return value?

I am starting to enjoy many features of Julia. I just haven’t found the best way to provide an optional return value. Lets say we have a function (improvised not compiled)

function myAlg(x::Float,y::Float)
    if x > y
        z=y;
        reason="A";
    else
        z=x;
        reason="B";
   end
   return z,reason
end

where reason is an additional return value that might provide more information but is not a result per se.

What is the best way to make that reason optional?
My first idea would be function myAlg(x::Float,y::Float; returnReason=false)

and a

if returnReason
    return z,reason
else
    return z
end

but is that type stable and intended that way?
Is there a better and nicer way to only get the reason returned if I explicitly ask for it?
What’s the best practice for such a szenario?

Kind Regards,
Kellertuer

You’re correct that your returnReason keyword argument will make the result type-unstable. One way you can get around this is by taking advantage of multiple dispatch by using a new type to indicate the kind of result you want. This is how, for example, Interpolations.jl works:

struct RequestReason # this immutable type has no fields, so constructing it is essentially free
end

function myAlg(x, y)
  return x + y
end

function myAlg(x, y, ::RequestReason)
  return x + y, "A"
end

So now you can call z = myAlg(x, y) or z, reason = myAlg(x, y, RequestReason()) and the result will be type-stable (since the return type is fully defined by the argument types).

It’s also worth noting that the compiler is improving in its ability to handle limited type-unstable code. In particular, in Julia v0.7, there’s some degree of constant propagation, so you can do:

julia> function myAlg(x, y, returnReason=false)
         z = x + y
         if returnReason
           return z, "A"
         else
           return z
         end
       end
myAlg (generic function with 2 methods)

julia> function g()
         z, reason = myAlg(1.0, 2.0, true)
       end
g (generic function with 1 method)

julia> @code_warntype g()
Body::Tuple{Float64,String}
2 1 ─ %1 = π (3.0, Float64)                                                                      │╻ myAlg
  │   %2 = Core.tuple(%1, "A")::Tuple{Float64,String}                                            ││
  └──      return %2  

Because the requestReason argument was set to the constant true inside the function g(), the compiler in v0.7 is able to propagate that constant through the body of myAlg and correctly infer that myAlg will return two arguments. However, I believe this constant propagation is not currently performed for keyword arguments (note that I made returnReason a positional argument in my definition above).

7 Likes

Thanks for the clarification – I first thought of kwargs because my function has them anyways, but maybe a positional one would do, too. I’m still on 0.6 so I might try the type-based approach, though in total I feel a little too typed already :wink:

Yeah, makes sense. A couple of other options for v0.6 would be:

  1. Always return both and let the caller decide whether to ignore the second result
  2. Always return both z and a Nullable{String}, only giving the nullable a value if the user requests it
  3. Just be type-unstable. Lots of good Julia code is type-unstable, and the performance impact can be pretty small. If this function gets called frequently in a loop, then type stability is important, but if it’s just called occasionally then it may not matter. Benchmarking with BenchmarkTools.jl will help show whether it’s worth even worrying about type stability here.
2 Likes

Maybe the third is a good idea, if – as you mentioned – it is improving in 0.7.
It is not a function called within a loop, even more I could to that in my outer wrapper having kwargs in order to provide a nice user access, since the inner one (just positional optional values) should be efficient already.
For the outer one I don’t care for efficiency that much. The main reason for the outer one is in fact to distinguish between an efficient inner implementation (with a not so nice interface, since on always has to remember all positions of optional arguments) and an outer function with a nice interface with optional kw-arguments, more validation, and better error messages and such.

That’s reasonable too.

By the way, it’s also worth noting that, while they don’t get the constant propagation yet, keyword arguments in v0.7 are actually fast (they are passed as named tuples instead of dictionaries). So you might find that it’s no longer necessary to have a special inner function that takes only positional arguments.

2 Likes

Any keyword function always called a special inner function that only had positional arguments.

Er, right, but it might not be necessary to write that inner function manually and ask users to call it just for performance reasons.

Yeah, sorry if I misunderstood you.

I also provide a different call mechanism (using two structs) for the user that way (in the idea of an experienced user calling the function often with different parameters) – so it’s also that the function without keyword arguments has its own reasoning. Still it’s nice to hear that in general it’s not necessary required to do two such functions.