# If blocks with code generation

**URL:** <https://discourse.julialang.org/t/if-blocks-with-code-generation/86506>\
**Category:** General Usage\
**Tags:** question, metaprogramming\
**Created:** [August 29, 2022, 4:31pm UTC](https://discourse.julialang.org/t/if-blocks-with-code-generation/86506 "2022-08-29T16:31:43Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![gaspardb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gaspardb/32/36536_2.png) [@gaspardb](https://discourse.julialang.org/u/gaspardb)\
**Post date:** [August 29, 2022, 4:31pm UTC](https://discourse.julialang.org/t/if-blocks-with-code-generation/86506/1 "2022-08-29T16:31:43Z")

</div>

Hi everyone,

I’ve started Julia recently and stumbled upon a case which seemed well suited to metaprogramming.

# Use case

With words: I have a `train` function which does roughly the same thing no matter the type of the model – declare variables, define a loss function, call this loss function – but still differs slightly depending on the model.  
Basically, my code is as follows (MRE below):

```julia
abstract type Meta end

struct A <: Meta end
struct B <: Meta end 

function train(x, t::A)
    # Block 1: declare variables
    model = ...

    function loss(model)
        # Block 2: compute things
    end 

    # Block 3: log values
    cur_loss = loss(model)
end 

function train(x, t::B)
    # Block 1: declare variables...
    model = ...
    # >> Add other variables 
    other_values = ...

    function loss(model, other_values)
        # Block 2: compute things
        # >> Specific things with other_values
    end 

    # Block 3: log values
    cur_loss = loss(model)
    # >> Custom metrics 
end 

```

To be clear, each `train` function have the same blocks (`1, 2, 3` in the example above) but differes marginally from each other. Sometimes I need to compute other variables, sometimes I need to call the loss with different values.  
I think this would work nicely with metaprogramming.

# Reproducing example

Let’s say I want to factorize the following

```julia
abstract type Meta end

struct A <: Meta end
struct B <: Meta end

function train(x, t::A)
    model = x
    function loss(model)
        model * 2
    end
    loss(model)
end

function train(x, t::B)
    model = x
    function loss(model)
        model * 3
    end
    loss(model)
end

```

I would love being able to write

```julia
for T ∈ (:A, :B)
    @eval function train_ifbranches(x, t::$T)
        model = x
        if $t == :B
            # declare other things
        end
        function loss(model)
            # with some @eval or whatever in front
            if $t == :A
                model * 2
            elseif $t == :B
                model * 3
            else
                0
            end
        end
        loss(model)
    end
end

```

# what I managed doing

```julia
for T ∈ (:A, :B)
    @eval function train_meta(x, t::$T)
        model = x
        function loss(model)
            model * $(:($T) == :A ? 2 : 3)
        end
        loss(model)
    end
end

```

however, I have multiple subtypes of `Meta` and can’t simply rely on ternary operator `?`. I can also declare an expression before and interpolate it, like

```julia-auto
for T ∈ (:A, :B)
    expr = if T == :A
        2
    else
        3
    end
    @show expr
    @eval function train_ifbranches(x, t::$T)
        model = x
        function loss(model)
            model * ($expr)
        end
        loss(model)
    end
end

```

but I would need to declare a lot of `expr`, and it would become unreadable.

# Final words

Clearly, I have not understand precisely the difference between the interpolating operator `$` and the `eval` function, even though I thought they were equivalent (according to [the docs](https://docs.julialang.org/en/v1/manual/metaprogramming/#Nested-quote)). Any help would be greatly appreciated! Of course this example is trivial, I did my best to simplify what I wanted to achieve and thought it would be better than pasting 100 lines of code. But I’d be happy to add details if that helps.

## Also: What I’ve tried besides metaprogramming

At first, I simply put `if` blocks everywhere, depending on the type of `t`. However, it was really cumbersome and induced some unrelated problems. Notably, I had to define several `loss` function (otw they would be overwritten) or I would be afraid that the autodiff package would suffer a performance penalty from checking multiple `if` branches. Anyways, I would be happy to see how MP works on this case.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [August 29, 2022, 4:42pm UTC](https://discourse.julialang.org/t/if-blocks-with-code-generation/86506/2 "2022-08-29T16:42:57Z")

</div>

> [@gaspardb](#):
>
> multiple subtypes of `Meta` and can’t simply rely on ternary operator `?`

I only skimmed your post, so I hope I didn’t misunderstand something, but are you aware that you can nest either `ifelse` or the ternary operator arbitrarily deep?

```julia
julia> f(n) = ifelse(iszero(n), 10, ifelse(isone(n), 100, 1000))
f (generic function with 1 method)

julia> g(n) = iszero(n) ? 10 : isone(n) ? 100 : 1000
g (generic function with 1 method)

julia> map(f, (0, 1, 2, 3))
(10, 100, 1000, 1000)

julia> map(g, (0, 1, 2, 3))
(10, 100, 1000, 1000)

```

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [August 29, 2022, 4:43pm UTC](https://discourse.julialang.org/t/if-blocks-with-code-generation/86506/3 "2022-08-29T16:43:39Z")

</div>

I wouldn’t use metaprograming for that. I would pass some additional parameters to your train function (perhaps a function as one of the parameters) and structure the code such that the differences can be managed by the parameters.

---

<div class="post-metadata">

**Author:** ![digital\_carver](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/digital_carver/32/33818_2.png) [@digital\_carver](https://discourse.julialang.org/u/digital_carver)\
**Post date:** [August 29, 2022, 5:19pm UTC](https://discourse.julialang.org/t/if-blocks-with-code-generation/86506/4 "2022-08-29T17:19:08Z")

</div>

I agree with @lmiq. Metaprogramming is super useful when it’s needed, but can also make your code harder to maintain, and should be reserved for cases where it’s really needed. Here, you can get a lot done with multiple dispatch on helper functions. For eg.

> [@gaspardb](#):
>
> ```julia
> function train(x, t::B)
> # Block 1: declare variables...
> model = ...
> # >> Add other variables 
> other_values = ...
> 
> ```

You can have a `get_other_values` function, with `get_other_values(t::B)` returning a named tuple of the other values, and `get_other_values(t::A)` returning `nothing`. Then within `loss`,

> [@gaspardb](#):
>
> ```
> function loss(model, other_values)
> # Block 2: compute things
> # >> Specific things with other_values
> end 
> 
> ```

you can have a `do_things_with_other_values` call, with one method of it accepting named tuples and computing things with its values, another method being `do_things_with_other_values(::Nothing) = return` .

These are just examples to illustrate the idea, a lot depends on the specifics of your code. Exploiting the type system and dispatch can give you performant, generic code, while being much more readable and maintainable than a metaprogramming approach.

---

<div class="post-metadata">

**Author:** ![gaspardb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gaspardb/32/36536_2.png) [@gaspardb](https://discourse.julialang.org/u/gaspardb)\
**Post date:** [August 29, 2022, 5:31pm UTC](https://discourse.julialang.org/t/if-blocks-with-code-generation/86506/5 "2022-08-29T17:31:32Z")

</div>

Thanks for your answers!  
Indeed that is what I tried in the first place but my main concern was not being able to write neat type-stable functions. On top of that, I had difficulties obtaining performant code with Enzyme for the autodiff, which made me a bit paranoid on the structure of the `loss` function. All this made me think that MP would be more efficient. But I’ll try your way @digital_carver and will let you know how that works!  
If someone else has suggestion on the MP approach I’d be interested still 🙂

---

<div class="post-metadata">

**Author:** ![digital\_carver](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/digital_carver/32/33818_2.png) [@digital\_carver](https://discourse.julialang.org/u/digital_carver)\
**Post date:** [August 29, 2022, 6:28pm UTC](https://discourse.julialang.org/t/if-blocks-with-code-generation/86506/6 "2022-08-29T18:28:17Z")

</div>

Yeah, those are understandable concerns.

I’d suggest trying out creative non-MP approaches first, including multiple dispatch and creating and using generic types where needed, but if it turns out you really do need metaprogramming ultimately, `@generated` might be the tool you need (and [CompTime.jl](https://www.youtube.com/watch?v=an6_N6RM8Zo) provides a nice layer on top of it to make it easier to use).

---

<div class="post-metadata">

**Author:** ![gaspardb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gaspardb/32/36536_2.png) [@gaspardb](https://discourse.julialang.org/u/gaspardb)\
**Post date:** [September 5, 2022, 10:03am UTC](https://discourse.julialang.org/t/if-blocks-with-code-generation/86506/7 "2022-09-05T10:03:05Z")

</div>

I ended up as @lmiq and @digital_carver suggested, and indeed I can see how using MP makes the code harder to maintain. I don’t even have type instability, so that’s good. Thanks for your help!
