Executing code in @eval

Hey all,

I don’t want to write lots of code and use @eval now. I want to generate different code depending on a condition inside @eval. It looks like this:

for (sym, len) in (
    (:sym1, nothing),
    (:sym2, 2)
)
    @eval struct $sym
        value::String
        function $sym(v::String)
            $( # This block doesn't work
                if len === nothing
                    nothing
                else
                    length(v) == len || throw(DomainError(v, "")) # Here the compiler says that it doesn't know `v`
                end
            )
            new(v)
        end
    end
    @eval Base.getindex(object::$sym) = object.value
    @eval Base.length(object::$sym) = length(object[])
end

Is there a way to execute code inside the expression I create for the @eval macro?

Thanks a lot!

This seems to work:

    @eval struct $sym
        value::String
        function $sym(v::String)
            $(
                if len === nothing
                    nothing
                else
                    :(length(v) == $len || throw(DomainError(v, "")))
                end
            )
            new(v)
        end
julia> sym2("aa")
sym2("aa")

julia> sym2("aaaa")
ERROR: DomainError with aaaa:

Stacktrace:
 [1] sym2(v::String)
   @ Main ./REPL[422]:8
 [2] top-level scope
   @ REPL[431]:1

julia> sym1("aaaa")
sym1("aaaa")

Are you sure this is the best way to handle your use case though? Obviously you are the best judge of that, and perhaps your real use is more complex, but for something like this I’d probably use a parametric type instead of defining types in a for loop

edit: looks like you can also simplify to

@eval struct $sym
    value::String
    function $sym(v::String)
        if $len !== nothing
            length(v) == $len || throw(DomainError(v, ""))
        end
        new(v)
     end
2 Likes

I want to use types for better readability. So one string for name, one for country and so on.

I like the first solution more. Because as I understand the first one does the check at compile time and the second at runtime. I hope that is correct :smiley:

In both cases, the check is done at compile time. The compiler does constant propagation and code elimination. For example, the following will compile to a no-op:

julia> function f()
           if 1+1 == 3
             error("Huh?")
           end
        end
f (generic function with 1 method)

julia> @code_llvm f()
;  @ REPL[1]:1 within `f'
define void @julia_f_206() {
top:
;  @ REPL[1]:3 within `f'
  ret void
}

(The test len === nothing, does not even depend on constant propagation. The compiler can figure it out from just the type information.)

2 Likes

To clarify what I meant, it would be something like

struct Sym{N}
    value::String
    function Sym{N}(v) where N
        N === nothing || @assert length(v) == N
        new{N}(v)
    end
end

# you can even make some aliases
for (sym, len) in ((:sym1, nothing), (:sym2, 2))
    @eval const $sym = Sym{$len}
end

Works the same way while also being more general, and requiring no metaprogramming.

julia> sym1("aaaa")
sym1("aaaa")

julia> sym2("aaaa")
ERROR: AssertionError: length(v) == N
Stacktrace:
 [1] sym2(v::String)
   @ Main ./REPL[16]:4
 [2] top-level scope
   @ REPL[24]:1

julia> sym2("aa")
sym2("aa")

julia> Sym{4}("aaaa") # can alias this one too if you want.
Sym{4}("aaaa")