`something` function is not that much useful with eager args eval, what about a macro one?

It’s more of an ifelse, since all its args are eagerly evaluated (think & vs &&, and why there is not an and function equivalent to &&). Use on real code™ can lead to significant penalty, if “else clause” args are computational intensive.

What about a macro variant @something ?

macro something(x, y...)
    :(let x_ = $(esc(x)) ;
        if x_ isa Nothing; (@something $(y...))
        elseif x_ isa Some; x_.value
        else; x_
        end
    end)
end

macro something()
    :(throw(ArgumentError("No value arguments present")))
end

tested by

using Test

# testing v1 - no regression
@test (@something nothing 1) == 1
@test (@something(Some(1), nothing)) == 1
@test (@something(missing, nothing)) === missing
@test_throws ArgumentError @something(nothing, nothing)

# testing v2 now
const A_CALLS = Ref{Int}(0)
struct A
    val
    A(a) = begin; global A_CALLS; A_CALLS[]+=1; new(a) end
end

# ... with better peanut butter
@test A_CALLS[] == 0
a1 = A(1)
@test A_CALLS[] == 1
@test (@something 1 A(1)) == 1 # A(1) not called since it's unnecessary
@test A_CALLS[] == 1

# ... meanwhile, once upon a time
A_CALLS[] = 0 # reset
@test something(1, A(1)) == 1
@test_broken A_CALLS[] == 0
@test A_CALLS[] == 1 # a new A has been called inadvertently with the new legacy o: one

Should this deserve a PR

It is in 1.7: https://github.com/JuliaLang/julia/pull/40729

2 Likes

Thanks for pointing that out so quickly !

BTW this new @something calls under the hood the old something function. It may a bit convoluted. This recursive macro seems to do the trick (following a good macro hygiene: expand and esc one and only once)