Is there a `with` operator or usage pattern?

Note that this doesn’t work at all within a function:

julia> function f(subtotal)
         @with subtotal begin
           price + shipping
         end
       end
f (generic function with 1 method)

julia> f(subtotal)
ERROR: MethodError: no method matching (::Base.Meta.var"#14#16")(; price=12.99, shipping=3.99)
Closest candidates are:
  #14(; price, shipping) at REPL[1]:16
Stacktrace:
 [1] macro expansion at ./REPL[1]:9 [inlined]
 [2] f(::NamedTuple{(:price, :shipping),Tuple{Float64,Float64}}) at ./REPL[8]:2
 [3] top-level scope at REPL[9]:1

and for the same reason it cannot be benchmarked with @btime from BenchmarkTools.

For a much simpler solution which works in local and global scope and imposes no performance penalty at all, see: With struct do - #12 by rdeits

For example:

julia> using MacroTools

julia> using MacroTools: postwalk

julia> macro with(container, expr)
         esc(postwalk(expr) do ex
           if @capture(ex, &field_)
             :(($container).$field)
           else
             ex
           end
         end)
       end
@with (macro with 1 method)

julia> subtotal = (price=12.99, shipping=3.99)
(price = 12.99, shipping = 3.99)

julia> @with subtotal begin
         &price + &shipping
       end
16.98

julia> function f(subtotal)
         @with subtotal begin
           &price + &shipping
         end
       end
f (generic function with 1 method)

julia> f(subtotal)
16.98

julia> using BenchmarkTools

julia> @btime f($subtotal)
  0.029 ns (0 allocations: 0 bytes)
16.98

Note that the suspiciously-fast benchmark time of < 1ns means that the compiler has actually eliminated all of the code in the benchmark and just replaced it with the answer. That’s actually great news for us–it means that the revised @with macro doesn’t make the code any harder for the compiler to analyze and optimize.

4 Likes