Something(a, b) not shortcutting

Assuming we have:

function do_work(x) 
    sleep(2)
    x
end

It turns out something(Some(2), do_work(3)) still invoke do_work, which returns 2 seconds later.
It would be better to define it as a macro, which retains the arguments as AST and only evaluate them when necessary.

This is simply eager evaluation. If you really need something to behave like this, you need to use a macro. Alternatively, && and || already short-circuits, so if it does no side effects, you can consider using those too.

1 Like

This is harder than I thought.

macro something()
    return :(throw(ArgumentError("No value arguments present")))
end
macro something(head, rest...)
    expr = quote
        if $head isa Nothing
            return  # somehow insert expansion of @something($(rest...)) at this position
        else
            return $head
        end
    end
    return expr
end

I wonder if it is even possible to make such a macro.

It’s possible: macros are arbitrary functions which transform syntax into syntax. One fairly direct way to write it could be as follows (there’s shorter ways but this should be relatively easy to understand):

function something_expr(ex, exs...)
    quote
        s = $(esc(ex))
        if isnothing(s)
            $(something_expr(exs...))
        else
            s
        end
    end
end
something_expr(ex) = :(something(ex))

macro something(exs...)
    something_expr(exs...)
end
7 Likes

Thank you very much! There is a error in the base case. Here is the complete code:

# https://discourse.julialang.org/t/something-a-b-not-shortcutting/27746/4.
# All credits to Chris Foster
function something_expr(head, rest...)
    quote
        head_val = $(esc(head))
        if head_val isa Some
            head_val.value
        elseif isnothing(head_val)
            $(something_expr(rest...))
        else
            head_val
        end
    end
end

function something_expr(head)
    quote
        something($(head))
    end
end

macro something(args...)
    something_expr(args...)
end

# test
function f(x)
    sleep(1)
    x
end

@something 1 2 # 1
@something 1 f(2) # 1, without delay
@something Some(1) 2 # 1
@something nothing 2 # 2
@something sleep(1) 2 # 2, 1s delay