# Macro to create a function with a variable number of elseif branches

**URL:** <https://discourse.julialang.org/t/macro-to-create-a-function-with-a-variable-number-of-elseif-branches/47665>\
**Category:** General Usage\
**Tags:** question, macros\
**Created:** [October 2, 2020, 8:52pm UTC](https://discourse.julialang.org/t/macro-to-create-a-function-with-a-variable-number-of-elseif-branches/47665 "2020-10-02T20:52:47Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![disberd](https://avatars.discourse-cdn.com/v4/letter/d/8edcca/32.png) [@disberd](https://discourse.julialang.org/u/disberd)\
**Post date:** [October 2, 2020, 8:52pm UTC](https://discourse.julialang.org/t/macro-to-create-a-function-with-a-variable-number-of-elseif-branches/47665/1 "2020-10-02T20:52:47Z")

</div>

Hello,

I am starting to try and use/understand meta-programming and wanted to create a macro to overload the `getproperty` function for custom types that are custom implementation of the Point in GeometryBasics (subtypes of StaticVectors) , and can have either 2 or 3 dimensions.

I wanted to create a macro `@custom_getproperty` that allowed to assign custom property names to this types so that for example I type:  
`@custom_getproperty MyType (:x,:y)`, the macro would generate a custom `getproperty` implementation that access x and y as the first and second element of the :data field of the custom type (:data is the only field of the type).

To try and have the capability of creating 2 or 3 elseif branches depending on the number of the propertynames provided as a tuple, I ended up creating this macro code.

```julia
 macro custom_getproperty(name,symbolstuple)
           a = "function Base.getproperty(obj::$(name),s::Symbol)
           if s == :data
               getfield(obj,:data)
           "
           for (i,sym) in enumerate(eval(symbolstuple))
               a *= "elseif s == $(Meta.quot(sym))
                   getfield(obj,:data)[$i]
               "
           end
           a *=
           "else
               error(\"The $(name) type does not have a \$(String(s)) property\")
           end
           end"

           esc(Meta.parse(a))
  end

```

While this appears to work in achieving the functionality I wanted, I do feel this is somehow a bad/evil way of dealing with this problem.  
Is there a better/cleaner way of achieving this functionality in a macro?

---

<div class="post-metadata">

**Author:** ![yuyichao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuyichao/32/20_2.png) [@yuyichao](https://discourse.julialang.org/u/yuyichao)\
**Post date:** [October 2, 2020, 9:07pm UTC](https://discourse.julialang.org/t/macro-to-create-a-function-with-a-variable-number-of-elseif-branches/47665/2 "2020-10-02T21:07:24Z")

</div>

Don’t use string and everything else is fine with this approach. Also `elseif`s are just nested `if`s

---

<div class="post-metadata">

**Author:** ![disberd](https://avatars.discourse-cdn.com/v4/letter/d/8edcca/32.png) [@disberd](https://discourse.julialang.org/u/disberd)\
**Post date:** [October 2, 2020, 9:18pm UTC](https://discourse.julialang.org/t/macro-to-create-a-function-with-a-variable-number-of-elseif-branches/47665/3 "2020-10-02T21:18:01Z")

</div>

By don’t use string you mean the last string inside the error, or don’t use strings at all in the macro?  
As I wouldn’t know how to make this macro without using strings followed by a Meta.parse.  
Could you give me an example In case?

Sorry if this is very basic but I am very new to metaprogramming

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [October 2, 2020, 9:19pm UTC](https://discourse.julialang.org/t/macro-to-create-a-function-with-a-variable-number-of-elseif-branches/47665/4 "2020-10-02T21:19:58Z")

</div>

[Metaprogramming · The Julia Language](https://docs.julialang.org/en/v1/manual/metaprogramming/#man-macros) has some good info. You can see that strings are not used.

---

<div class="post-metadata">

**Author:** ![disberd](https://avatars.discourse-cdn.com/v4/letter/d/8edcca/32.png) [@disberd](https://discourse.julialang.org/u/disberd)\
**Post date:** [October 2, 2020, 10:03pm UTC](https://discourse.julialang.org/t/macro-to-create-a-function-with-a-variable-number-of-elseif-branches/47665/5 "2020-10-02T22:03:14Z")

</div>

Thanks,  
I actually have read the metaprogrammin section of the manual multiple times, and it went from completely obscure at the beginning of my julia journey to always a bit more understandable.

While I did see that there are no string in the example macros of the manual, I can not think of another way of generating a code whose inner part has a variable number of elseif statements that depends on the value of one of the macro arguments.  
If for example I knew that the number of properties in my example was just 2, I know I could make the macro without strings as this:

```julia
macro custom_getproperty2(name,symbolstuple)
    esc(
        quote
            function Base.getproperty(obj::$(name),s::Symbol)
                if s == :data
                    getfield(obj,:data)
                elseif s == $(symbolstuple)[1]
                    getfield(obj,:data)[1]
                elseif s == $(symbolstuple)[2]
                    getfield(obj,:data)[2]
                else
                    error("The $(name) type does not have a $(String(s)) property")
                end
            end
        end 
    )
  end

```

At the same time, if I had certainly 3 elements I could do basically the same by adding by hand another elseif in the quote block.  
The problem if that I do not know how to generate such code programmatically depending on the number of the elements in the tuple argument (outside of my code at the beginning using strings).  
My initial idea was to generate the the first if part, then a variable number of elseif expressions, and then the last part and concatenate all the expressions together.  
The problem is that I don’t know how to concatenate expressions together.  
I tried creating an expression with `exp = :(if cond statement end)` and appending another if expression as last element like `exp.args[3] = :(if cond2 statement2 end)` (trying to interpret the statement from @yuyichao that elseif are just nested ifs) but I get an error.

That was why I would be very grateful if someone could give me an example to replicate my code in the original post but without the strings, so that I could try to reverse engineer the code to try understanding :).

---

<div class="post-metadata">

**Author:** ![yuyichao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuyichao/32/20_2.png) [@yuyichao](https://discourse.julialang.org/u/yuyichao)\
**Post date:** [October 2, 2020, 10:44pm UTC](https://discourse.julialang.org/t/macro-to-create-a-function-with-a-variable-number-of-elseif-branches/47665/6 "2020-10-02T22:44:31Z")

</div>

You can figure out how to generate the code you want simply by `dump`ing or `Meta.show_sexpr` the expression.

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [October 2, 2020, 11:04pm UTC](https://discourse.julialang.org/t/macro-to-create-a-function-with-a-variable-number-of-elseif-branches/47665/7 "2020-10-02T23:04:01Z")

</div>

> [@disberd](#):
>
> I can not think of another way of generating a code whose inner part has a variable number of elseif statements that depends on the value of one of the macro arguments.

I see, maybe you can use this example for something:

```julia
macro n_nested_ifs(x, n)  
    orig_expr = Expr(:if, :($(esc(x)) == 1), :(print(1)))
    expr = orig_expr
    for i in 1:n-1
        push!(expr.args, Expr(:elseif, :($(esc(x)) == $i), :(print($i))))
        expr = expr.args[end]
    end
    push!(expr.args, :(error("failed to match")))
    return orig_expr
end

@macroexpand @n_nested_ifs(a, 5)

```

---

<div class="post-metadata">

**Author:** ![tomerarnon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tomerarnon/32/3170_2.png) [@tomerarnon](https://discourse.julialang.org/u/tomerarnon)\
**Post date:** [October 2, 2020, 11:21pm UTC](https://discourse.julialang.org/t/macro-to-create-a-function-with-a-variable-number-of-elseif-branches/47665/8 "2020-10-02T23:21:12Z")

</div>

@kristoffer.carlsson beat me to it. Is there any practical difference between using `elseif` vs a series of `&&`?

```julia
macro custom_getproperty(name, symbolstuple...)
    methoddef = :(function Base.getproperty(obj::$name, s::Symbol) end)
    functionblock = last(methoddef.args).args

    for (i, sym) in enumerate(symbolstuple)
        if_case = :(s === $(symbolstuple[i]) && return getfield(obj, :data)[$i])
        push!(functionblock, if_case)
    end

    push!(functionblock, :(error("type $($name) has no field $s")))

    esc(methoddef)
end

```

---

<div class="post-metadata">

**Author:** ![disberd](https://avatars.discourse-cdn.com/v4/letter/d/8edcca/32.png) [@disberd](https://discourse.julialang.org/u/disberd)\
**Post date:** [October 3, 2020, 9:07am UTC](https://discourse.julialang.org/t/macro-to-create-a-function-with-a-variable-number-of-elseif-branches/47665/9 "2020-10-03T09:07:21Z")

</div>

Thanks a lot both to @kristoffer.carlsson and @tomerarnon, both these alternative approaches are insightful

---

<div class="post-metadata">

**Author:** ![disberd](https://avatars.discourse-cdn.com/v4/letter/d/8edcca/32.png) [@disberd](https://discourse.julialang.org/u/disberd)\
**Post date:** [October 3, 2020, 10:06am UTC](https://discourse.julialang.org/t/macro-to-create-a-function-with-a-variable-number-of-elseif-branches/47665/10 "2020-10-03T10:06:40Z")

</div>

There is actually a strange thing happening with the interpolation in the last error string.

If I try to do a @macroexpand this is what I get

```julia
@macroexpand @custom_getproperty Point2 :x :y
:(function Base.getproperty(obj::Point2, s::Symbol)
      #= Untitled-1:41 =#
      #= Untitled-1:41 =#
      s === :x && return (getfield(obj, :data))[1]
      s === :y && return (getfield(obj, :data))[2]
      error("type $(Point2) has no field $(s)")
  end)

```

As you see the string for the error does not directly put Point2 inside but try to interpolate the value of the variable Point2.

By looking at the `dump` of the correct `error` expression and trying to use the code from @kristoffer.carlsson I came up with this substitution for the last line  
`push!(functionblock, Expr(:call,:error,Expr(:string,"Type $(name) does not have property ",:s)))`  
This works but is there any easier way of achieving the correct string interpolation inside the error?

---

<div class="post-metadata">

**Author:** ![ffevotte](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffevotte/32/6587_2.png) [@ffevotte](https://discourse.julialang.org/u/ffevotte)\
**Post date:** [October 3, 2020, 12:31pm UTC](https://discourse.julialang.org/t/macro-to-create-a-function-with-a-variable-number-of-elseif-branches/47665/11 "2020-10-03T12:31:57Z")

</div>

maybe something like this:

```julia
push!(functionblock, :(error("type ", $(string(name)), " has no field $s")))

```

or this (if you manage wrapping your head around the nested interpolation):

```julia
push!(functionblock, :(error("type $($(string(name))) has no field $s")))

```
