How do I assign and branch?

Here’s some code.

tmp = myFunction()
if !isnothing(tmp)
    doSomethingWith(tmp)
end

Some languages have a convenient way to express this. The disadvantage of what I have done is introduce a new temporary variable tmp, which serves no purpose other than as an optimization.

The optimization avoids calling myFunction() twice.

if !isnothing(myFunction())
    doSomethingWith(myFunction())
end

Is there a better way to express what I want to do here?

You could define

dosomething(::Nothing) = nothing 
dosomething(val) = # do something 

And then call

dosomething(foo())

directly.

7 Likes

Use a higher-order function:

function onsomething(f, val)
    if !isnothing(val)
       f(val)
    end
end

This can be used without naming the (tmp) argument

onsomething(doSomethingWith, foo())

or the do-syntax:

onsomething(foo()) do tmp  # name it here
    doSomethingWith(tmp)
end
6 Likes

These are both really good suggestions, and not at all what I was imagining would be the solution. Thank you both for your comments

On some level, the logic for the convenient expression has to be handled separately, and that is usually put in a higher-order function and sometimes a macro.

To comment on the temporary variable, if your concern is less about having to write it but about it sticking around in a language where global variables don’t go away, you could either work inside a local scope or declare local tmp in an otherwise globally-scoped top-level expression (begin, if) to make local variables go away.

You can do:

julia> function foo()
           println("hello")
           return 1
       end
foo (generic function with 1 method)

julia> if (tmp = foo()) !== nothing
           sin(tmp)
       end
hello
0.8414709848078965
3 Likes

Adding currying to @bertschi’s suggestion is nice too.

onsomething(f) = x -> onsomething(f, x)

onsomething(doSomethingWith)(foo())

Has a similar flavor ask skipmissing!

To clarify what some people above have already hinted, there is no performance penalty to additional variables. The compiler will create a “variable” for the result of myFunction() whether you assign it to a named variable or not (unless it is eliminated as dead code).

julia> code_lowered(function (a); b = a; c = b; d = c; return d*d; end, (Float64,))
1-element Vector{Core.CodeInfo}:
 CodeInfo(
1 ─ %1 = a
│        b = %1
│   %3 = b
│        c = %3
│   %5 = c
│        d = %5
│   %7 = d
│   %8 = d
│   %9 = %7 * %8
└──      return %9
)

julia> code_llvm(function (a); b = a; c = b; d = c; return d*d; end, (Float64,); debuginfo=:none)
; Function Signature: var"#45"(Float64)
define double @"julia_#45_2173"(double %"a::Float64") #0 {
top:
  %0 = fmul double %"a::Float64", %"a::Float64"
  ret double %0
}

See that the code_lowered shows many assignments to temporary variables like %1 (with more assignments than we even wrote, in fact). A named variable is just a value that has a name you can refer to it later, but the values are produced in any case.

But by the time we reach the code_llvm, all of those have vanished and it simply does the “equivalent” operation.

So if you are concerned about the performance cost of assigning a new variable, don’t be. If the namespace pollution bothers you, give the result a better name than tmp and it won’t feel so bad. If you really want the variable name gone anyway, then a let could do what you want (but has no performance benefit in this situation):

let tmp = myFunction()
    # tmp only exists within this block
    if !isnothing(tmp)
        doSomethingWith(tmp)
    end
end

Note that, in some situations, the compiler may be able to eliminate a second call to myFunction() and simply recycle the result of the first (the compiler must prove it will give the same result). But it’s better not to rely on that (preferring the tmp = myFunction() idiom) when you know you want it.

4 Likes

Thanks - another good solution

Some alternatives (similar to previous ones)

myFunction() |> x->isnothing(x) || doSomethingWith(x)
(tmp = myFunction()) === nothing || doSomethingWith(tmp)