# Is there a reason to use \`@expression\`? I think it's redundant

**URL:** <https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829>\
**Category:** Optimization (Mathematical)\
**Tags:** question, jump, gurobi\
**Created:** [August 25, 2025, 8:33am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829 "2025-08-25T08:33:43Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [August 25, 2025, 8:33am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/1 "2025-08-25T08:33:43Z")

</div>

Sometimes I build incorrect expressions obliviously, but JuMP won’t throw an ERROR. Is this good?

```julia
import JuMP, Gurobi
model1 = JuMP.Model(Gurobi.Optimizer);
JuMP.@variable(model1, x1 >= 1);
JuMP.@objective(model1, Min, x1);
JuMP.optimize!(model1);

model2 = JuMP.Model(Gurobi.Optimizer);
JuMP.@variable(model2, x2);
JuMP.@objective(model2, Min, x2);

# I miswrote `model2` as `model1` here
expr_miswrote = JuMP.@expression(model1, JuMP.value(x1)x2)
# But I fail to receive a proper ERROR
# And it can work normally later on!
JuMP.@constraint(model2, expr_miswrote >= 2)
JuMP.optimize!(model2)
JuMP.assert_is_solved_and_feasible(model2; allow_local = false)
@assert JuMP.objective_value(model2) == 2

```

Two unusual things to note:

1. `JuMP.value(x1)x2` belongs to `model2` indeed, but it can be associated to `model1`.
2. At least by inspecting the code, `expr_miswrote` belongs to `model1`, but it can be inserted into a `model2`’s constraint.

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [August 25, 2025, 9:10pm UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/2 "2025-08-25T21:10:01Z")

</div>

This is expected behavior, even if it might look confusing.

Your `expr_miswrote` contains variables from `model2`. But you can “add” it as an expression to `model1` because it never actually gets added to the objective or constraints of `model1`.

We allow it because you can actually add expressions of any type to a model.

```Julia
julia> using JuMP

julia> model = Model();

julia> @expression(model, my_expr[i in 1:2], Dict(:foo => i))
2-element Vector{Dict{Symbol, Int64}}:
 Dict(:foo => 1)
 Dict(:foo => 2)

julia> model[:my_expr]
2-element Vector{Dict{Symbol, Int64}}:
 Dict(:foo => 1)
 Dict(:foo => 2)

```

You can use `expr_miswrote` in a constraint of `model2` because that is perfectly legal: it contains only variables from `model2`.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [August 26, 2025, 12:56am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/3 "2025-08-26T00:56:05Z")

</div>

> [@odow](#):
>
> ```julia-auto
> julia> @expression(model, my_expr[i in 1:2], Dict(:foo => i))
> julia> model[:my_expr]
> 
> ```

This makes some sense as `my_expr` becomes one of the belongings of `model`.

> [@WalterMadelim](#):
>
> `expr_miswrote = JuMP.@expression(model1, JuMP.value(x1)x2)`

But the arg `model1` in this anonymous usage becomes pointless.  
To build an anonymous expression, it suffices to write  
`expr = JuMP.@build_expression(JuMP.value(x1)x2)`  
resembling  
`con = JuMP.@build_constraint(expr <= 2)`, no?

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [August 26, 2025, 1:23am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/4 "2025-08-26T01:23:06Z")

</div>

> To build an anonymous expression, it suffices to write

I assume you meant this as a potential feature request. Currently `@build_expression` doesn’t exist.

I don’t plan to make any changes to JuMP related to this issue. Yes, you can create expression in the “wrong” model, but changing this is a breaking change we won’t be doing. We also won’t be adding any new macros just to support anonymous expressions that are not affiliated with a model.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [August 26, 2025, 1:28am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/5 "2025-08-26T01:28:37Z")

</div>

> [@odow](#):
>
> I assume you meant this as a potential feature request.

No, I just express my thoughts.

Maybe this can be improved in JuMP 2.0 or some version that permit breaking changes.

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [August 26, 2025, 2:08am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/6 "2025-08-26T02:08:38Z")

</div>

> [@WalterMadelim](#):
>
> Maybe this can be improved in JuMP 2.0 or some version that permit breaking changes.

We have no plans to work on or release JuMP 2.0

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [September 22, 2025, 4:25am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/7 "2025-09-22T04:25:06Z")

</div>

The construct `@expression` is odd—I surmise that in _all_ circumstances it’s better to bypass using it, so we can remove the `@expression` API entirely.

Say,

- I want to create a new expression `e` using existing variables `x` and `y`.

The standard way at present:

`@expression(model, e, 1x + 2y)`

Instead of it, Why not write:

`@variable(model, e == 1x + 2y) # I currently propose this grammar`

so that

`JuMP.is_fixed(e)` # true

`JuMP.FixRef(e)` # refering to the constraint `e == 1x + 2y` built above

`JuMP.is_integer(e)` # false

`JuMP.set_integer(e)` # you can do this

Additionally you can do e.g.

`JuMP.set_upper_bound(e, 9)`

If you also want to “fix `e` to `4`” in the _old_ sense, despite being highly unusual and unlikely in practice, you can still do

`JuMP.@constraint(model, c, e == 4)`

so we won’t be losing anything.

* * *

If a user want to write

`@variable(model, x == 4)`

in the old sense.

I would say: if he doesn’t need dual variable, then he can write  
`set_upper_bound` + `set_lower_bound` at the same time to bypass.

If he need the dual associated to `x == 4`, then probably `x` will not serve as an expression.  
so the new grammar proposed by me won’t clash with his intention.

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [September 22, 2025, 4:34am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/8 "2025-09-22T04:34:28Z")

</div>

AMPL calls these “defined variables”: [https://ampl.com/wp-content/uploads/Chapter-8-Linear-Programs-Variables-Objectives-and-Constraints-AMPL-Book.pdf](https://ampl.com/wp-content/uploads/Chapter-8-Linear-Programs-Variables-Objectives-and-Constraints-AMPL-Book.pdf)

We could have chosen to do something similar, but we didn’t.

> so we can remove the `@expression` API entirely.

Since JuMP has reached v1, we will never do this.

As a corollary, since we have `@expression`, we won’t be adding a way to replicate it via `@variable`.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [September 22, 2025, 6:53am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/9 "2025-09-22T06:53:05Z")

</div>

Is this a bug?

```julia
julia> import JuMP

julia> model = JuMP.Model();

julia> x = map(_ -> JuMP.@variable(model), 1) # returns a SCALAR
_[1]

julia> ndims(x), ndims(1)
(0, 0)

julia> map(_ -> JuMP.@variable(model), x) # returns a Vector???
1-element Vector{JuMP.VariableRef}:
 _[2]

```

I think the last return should be `_[2]` solely, not wrapped in a Vector.  
Yes, surely I think `_[2]` should be the expected return, otherwise duplicating the following:

```julia
julia> model = JuMP.Model();

julia> x = map(_ -> JuMP.@variable(model), 1)
_[1]

julia> map(_ -> JuMP.@variable(model), [x])
1-element Vector{JuMP.VariableRef}:
 _[2]

```

The julia’s behavior is sane:

```julia-auto
julia> map(_ -> 1, 2)
1

julia> map(_ -> 1, [2])
1-element Vector{Int64}:
 1

```

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [September 22, 2025, 8:48am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/10 "2025-09-22T08:48:42Z")

</div>

This behaviour isn’t specific to JuMP:

```Julia
julia> struct Foo end

julia> Base.length(::Foo) = 1

julia> Base.iterate(f::Foo) = (f, true)

julia> Base.iterate(f::Foo, state) = nothing

julia> map(_ -> :a, 1)
:a

julia> map(_ -> :a, Foo())
1-element Vector{Symbol}:
 :a

```

There’s special fallback for `Number`:

> <https://github.com/JuliaLang/julia/blob/f2b3dbda30a314d042178ec70ae2115735176f7e/base/number.jl#L284>

Arguably that is the inconsistent one here.

We probably won’t be changing the current behaviour in JuMP. It’s also arguably correct, so a change is breaking. But also. Why would you `map` over a variable. Just don’t do that.

If I had a comment, it’s that many of your recent Discourse questions are getting at edge cases of Julia. Sure, some of them are confusing. But in every case, it’s because you are writing code that is not idiomatic.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [September 22, 2025, 9:07am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/11 "2025-09-22T09:07:54Z")

</div>

> [@odow](#):
>
> Why would you `map` over a variable.

Because my API works inconsistently. I don’t think there is anywhere wrong with my macro API.

```julia
julia> macro get_int_decision(model, expr) return esc(quote
           let e = JuMP.@expression($model, $expr)
               local a = map(_ -> JuMP.@variable($model, integer = true), e)        
               JuMP.@constraint($model, a == e)
               a
           end
       end) end
@get_int_decision (macro with 1 method)

julia> 

julia> import JuMP

julia> m = JuMP.Model();

julia> JuMP.@variable(m, x);

julia> @get_int_decision(m, [1x, 2x]); # okay

julia> @get_int_decision(m, 1x) # ERROR
ERROR: Subtraction between an array and a JuMP scalar is not supported: instead of `x - y`, do `x .- y` for element-wise subtraction.

```

> [@odow](#):
>
> There’s special fallback for `Number`:

I think there should be a counterpart in JuMP. Since

```julia
julia> ndims(x), ndims(1)
(0, 0)

```

, they should be consistent. Otherwise driving anyone who has the slightest bit of knowledge about linear algebra mad.

> [@odow](#):
>
> so a change is breaking

This should be breaked. The existing behavior is wrong. (And it’s inconsistent with the Julia’s spirit on Linear Algebra). `x::VariableRef` is the JuMP’s counterpart of `1.0::Float64` in julia—linked by `JuMP.value`

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [September 22, 2025, 9:11am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/12 "2025-09-22T09:11:36Z")

</div>

> [@WalterMadelim](#):
>
> Because my API works inconsistently

You probably want `JuMP.@constraint($model, a .== e)`.

There are many inconsistencies in Julia. It doesn’t have strong interfaces so there is no reason one is more correct than another.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [September 22, 2025, 9:18am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/13 "2025-09-22T09:18:53Z")

</div>

> You probably want

No this is not decent. If the RHS is a scalar the LHS is a Array I would use `.==`.  
But here I’m certain that they should be the same shape, therefore I would use the special method `==` exclusive for vector relation (doing things in a Vector Space in the math sense).

> It doesn’t have strong interfaces

What does this mean?

* * *

Edit: Okay, I know it’s a bit vague, let’s shelve this issue.🙃

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [September 23, 2025, 12:57am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/14 "2025-09-23T00:57:39Z")

</div>

Currently there are 2 ways out:

1. use `broadcast` instead of `map`
2. add a `ndims` branch before using `map`

I think I prefer the latter.

```julia
julia> import JuMP.value as ı

julia> x # a VariableRef after optimize!
_[1]

julia> ı(x) # get its value as a scalar
0.0

julia> f = z -> round(Bool, ı(z));

julia> f(x) # to be saved as a Bool
false

julia> f.([x, 1x]) # works
2-element BitVector:
 0
 0

julia> f.(x) # This is correct though
false

julia> broadcast(f, x) # unlike `map`, this returns an expected object
false

julia> function get_Bool_value(x) # I prefer this API
           f = z -> round(Bool, ı(z))
           ndims(x) == 0 && return f(x)
           map(f, x)
       end

julia> get_Bool_value(x) # works for scalar
false

julia> get_Bool_value([x, 1x]) # works for arrays
2-element Vector{Bool}:
 0
 0

```

* * *

I somewhat doubt the performance of broadcasting—another reason for me to prefer `map`. e.g. [Add vector version for setting constraint attributes by blegat · Pull Request #4033 · jump-dev/JuMP.jl · GitHub](https://github.com/jump-dev/JuMP.jl/pull/4033#issuecomment-3111836231)

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [February 8, 2026, 1:04am UTC](https://discourse.julialang.org/t/is-there-a-reason-to-use-expression-i-think-its-redundant/131829/15 "2026-02-08T01:04:58Z")

</div>

I admit the current API is good, so it is not redundant.

The `JuMP.@expression` macro also serves as a convenient register. e.g. this example

```julia-auto
julia> m
A JuMP Model
├ mode: DIRECT
├ solver: Gurobi
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none

julia> JuMP.@expression(m, c, [JuMP.VariableRef[] for j=1:3]);

julia> c = JuMP.@variable(m, lower_bound = 0);

julia> push!(m[:c][2], c);

julia> c = JuMP.@variable(m, lower_bound = 0);

julia> push!(m[:c][2], c);

julia> JuMP.@constraint(m, sum(m[:c][2]) == 1);

julia> print(m)
Feasibility
Subject to
 _[1] ≥ 0
 _[2] ≥ 0
 _[1] + _[2] = 1

```
