# Typed globals: macro to define multiple, without manual type annotations

**URL:** <https://discourse.julialang.org/t/typed-globals-macro-to-define-multiple-without-manual-type-annotations/90818>\
**Category:** General Usage\
**Tags:** macros, typed-globals\
**Created:** [November 25, 2022, 4:46pm UTC](https://discourse.julialang.org/t/typed-globals-macro-to-define-multiple-without-manual-type-annotations/90818 "2022-11-25T16:46:59Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![tfiers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tfiers/32/32427_2.png) [@tfiers](https://discourse.julialang.org/u/tfiers)\
**Post date:** [November 25, 2022, 4:46pm UTC](https://discourse.julialang.org/t/typed-globals-macro-to-define-multiple-without-manual-type-annotations/90818/1 "2022-11-25T16:46:59Z")

</div>

I vaguely remember seeing a macro somewhere to define typed globals without having to give the type explicitly, something like

```julia
@typed begin
  a = 2
  b = 2.8
  ⋮
end

```

which would expand to

```julia
a::Int = 2
b::Float64 = 2.8
⋮

```

Does anyone remember where that was?

---

<div class="post-metadata">

**Author:** ![simeonschaub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simeonschaub/32/216566_2.png) [@simeonschaub](https://discourse.julialang.org/u/simeonschaub)\
**Post date:** [November 27, 2022, 11:38am UTC](https://discourse.julialang.org/t/typed-globals-macro-to-define-multiple-without-manual-type-annotations/90818/2 "2022-11-27T11:38:10Z")

</div>

Not sure if something like this is already in a package somewhere, but here’s one way to write such a macro:

```julia
function rewrite_assignment(@nospecialize(ex))
    if Meta.isexpr(ex, :(=))
        l, r = ex.args
        l isa Symbol || error("Destructuring not supported")
        tmp = gensym(l)
        return esc(:(let $tmp = $r; global $l::typeof($tmp) = $tmp end))
    elseif Meta.isexpr(ex, :block)
        return Expr(:block, Base.mapany(rewrite_assignment, ex.args)...)
    else
        return ex
    end
end

macro typed(ex)
    return rewrite_assignment(ex)
end

```

```julia
julia> @typed begin
         a = 2
         b = 2.8
       end
2.8

julia> code_typed() do
           a, b
       end
1-element Vector{Any}:
 CodeInfo(
1 ─ %1 = Main.a::Int64
│ %2 = Main.b::Float64
│ %3 = Core.tuple(%1, %2)::Tuple{Int64, Float64}
└── return %3
) => Tuple{Int64, Float64}

```

---

<div class="post-metadata">

**Author:** ![tfiers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tfiers/32/32427_2.png) [@tfiers](https://discourse.julialang.org/u/tfiers)\
**Post date:** [November 27, 2022, 10:07pm UTC](https://discourse.julialang.org/t/typed-globals-macro-to-define-multiple-without-manual-type-annotations/90818/3 "2022-11-27T22:07:54Z")

</div>

Amazing, thanks!

> [@simeonschaub](#):
>
> `global $l::typeof($tmp) = $tmp`

Could we write `$l::typeof($r) = $r` instead? (i.e. w/o `let` and `tmp`)

---

<div class="post-metadata">

**Author:** ![simeonschaub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simeonschaub/32/216566_2.png) [@simeonschaub](https://discourse.julialang.org/u/simeonschaub)\
**Post date:** [November 27, 2022, 10:21pm UTC](https://discourse.julialang.org/t/typed-globals-macro-to-define-multiple-without-manual-type-annotations/90818/4 "2022-11-27T22:21:48Z")

</div>

> [@tfiers](#):
>
> Could we write `$l::typeof($r) = $r` instead? (i.e. w/o `let` and `tmp`)

That’s fine if the right-hand side is a literal or symbol, but you might also have an expression with side effects, for example something like `@typed a = pop!(v)`. Or even one that returns different results each time like `@typed a = rand((1, 2.0))`. In those cases you want to avoid evaluating `r` twice.

---

<div class="post-metadata">

**Author:** ![tfiers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tfiers/32/32427_2.png) [@tfiers](https://discourse.julialang.org/u/tfiers)\
**Post date:** [November 28, 2022, 4:59pm UTC](https://discourse.julialang.org/t/typed-globals-macro-to-define-multiple-without-manual-type-annotations/90818/5 "2022-11-28T16:59:28Z")

</div>

Just adding this for reference: “ **typeconst** ” is a relevant keyword for this problem on the discourse: [Search results for 'typeconst' - Julia Programming Language](https://discourse.julialang.org/search?q=typeconst) (thanks Stefan Karpinski)

---

<div class="post-metadata">

**Author:** ![tfiers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tfiers/32/32427_2.png) [@tfiers](https://discourse.julialang.org/u/tfiers)\
**Post date:** [December 9, 2022, 1:57am UTC](https://discourse.julialang.org/t/typed-globals-macro-to-define-multiple-without-manual-type-annotations/90818/6 "2022-12-09T01:57:35Z")

</div>

Ah, found what I was originally thinking of: it’s the “@stable” macro by @giordano in the Seven Lines of Julia thread:

> [@Seven Lines of Julia (examples sought)](https://discourse.julialang.org/t/seven-lines-of-julia-examples-sought/50416/157):
>
> In Julia v1.8 we’ll have the possibility of type-annotating global variables, to promise their type won’t change: julia\> x::Float64 = 3.14 3.14 julia\> x = 2.71 2.71 julia\> x = "hello world" ERROR: MethodError: Cannot `convert` an object of type String to an object of type Float64 Wouldn’t it be nice if we could automatically get the type annotation without having to explicitly type it ourselves? Here be dragons macros (note that the macro definition is exactly 7 lines): julia\> macro stable…

Simeon’s is a bit robuster indeed, with the $tmp.

@simeonschaub I was also wondering, why the `Base.mapany` (and not just `map`)?  
↓  
EDIT: I found why [here](https://timholy.github.io/SnoopCompile.jl/stable/snoopr/#Adding-type-annotations) (in the SnoopCompile docs) (maybe):

> `mapany` avoids trying to narrow the type of `f(v[i])` and just assumes it will be `Any`, thereby avoiding invalidations of many `convert` methods.

So, it’s used to spare the compiler some type inference work and/or to avoid `convert` invalidations, I think

The final thing I wonder about is the @nospecialize:  
all Exprs are the same type, so there is no “too many specializations” problem right?

Like, why not say `rewrite_assignment(ex::Expr)`.  
And why the `Meta.isexpr(ex, …)` instead of just `ex.head == …`?  
↓  
Ah, these are to handle `LineNumberNode`s, I assume.

Still, how is the @nospecialize useful?

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [December 13, 2022, 9:31am UTC](https://discourse.julialang.org/t/typed-globals-macro-to-define-multiple-without-manual-type-annotations/90818/7 "2022-12-13T09:31:30Z")

</div>

> [@tfiers](#):
>
> Simeon’s is a bit robuster indeed, with the $tmp.

I think I’m missing why that’s more robust (than what?)

---

<div class="post-metadata">

**Author:** ![tfiers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tfiers/32/32427_2.png) [@tfiers](https://discourse.julialang.org/u/tfiers)\
**Post date:** [December 13, 2022, 11:49am UTC](https://discourse.julialang.org/t/typed-globals-macro-to-define-multiple-without-manual-type-annotations/90818/8 "2022-12-13T11:49:01Z")

</div>

Sorry, I hadn’t looked at your ‘seven lines’ well.  
I thought it said something like

```julia
:(
    $lhs::typeof($rhs) = $rhs 
)

```

(With left- and right-hand sides `lhs, rhs = ex.args`).

But instead it uses a `local` temporary variable.  
I.e. (rewriting a bit for ease of comparison):

```julia
:(
    begin
        local $tmp = $rhs
        $(lhs)::typeof($tmp) = $tmp
    end
)

```

(with `tmp = gensym(lhs)`, for when applying the macro to more than one assignment at once)

…which I suppose is equivalent to Simeon’s `let` & `global` approach:

```julia
:(
    let $tmp = $rhs
        global $(lhs)::typeof($tmp) = $tmp
    end
)

```

---

<div class="post-metadata">

**Author:** ![tfiers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tfiers/32/32427_2.png) [@tfiers](https://discourse.julialang.org/u/tfiers)\
**Post date:** [December 13, 2022, 12:00pm UTC](https://discourse.julialang.org/t/typed-globals-macro-to-define-multiple-without-manual-type-annotations/90818/9 "2022-12-13T12:00:00Z")

</div>

As an addition, in my code where I define a macro like this, I added branch on whether `typeof(rhs) ∈ [Expr, Symbol]` or not.

If it is, I do as above (i.e. the `$tmp` approach, to handle non-determinism and side effects).

But if not (i.e. the right-hand side is a literal (hopefully)), I `eval` the `typeof` already in the macro:

```julia
T = Symbol(@eval typeof($rhs))
:(
    $lhs::$T = $rhs
)

```

The goal is to show something less scary to users when they e.g. `@macroexpand`, in the simple (and most common) cases.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [December 13, 2022, 12:01pm UTC](https://discourse.julialang.org/t/typed-globals-macro-to-define-multiple-without-manual-type-annotations/90818/10 "2022-12-13T12:01:00Z")

</div>

For the record, I had written a blog post about a slightly more elaborate version of my macro:

> **[My first macro in Julia](https://giordano.github.io/blog/2022-06-18-first-macro/)**
>
> This post isn’t about the first macro I wrote in the Julia programming language, but it can be about your first macro. Frequently Asked Questions Question: What are macros in Julia? Answer: Macros are sort of functions which take as input unevaluated...
