What is a GlobalRef and how I use it?

Motivation

RecipesBase.jl defines a macro @recipe that adds new methods to the RecipesBase.apply_recipe function. The catch is that RecipesBase might not be in scope at the macro call site, so the function RecipesBase.apply_recipe must be passed in directly.

I first tried interpolation

:($apply_recipe(x) = x+1)

But that failed because functions cannot be interpolated into function declarations. I thaught this was a bug and opened an issue, but apparently I’m supposed to be using GlobalRefs instead.

I tried this

:(Expr(:call, GlobalRef(RecipesBase, :apply_recipe), :x) = x+1)

But got segfaults on some downstream tests.

Finally, I tried

:(let M = $RecipesBase
    M.apply_recipe(x) = x+1
end)

Which works but seems needlessly complex.

PR: [RecipesBase] Use interpolation instead of qualification to drop dependance on the namespace of RecipesBase users by LilithHafner · Pull Request #4559 · JuliaPlots/Plots.jl · GitHub

Questions

Why does the second approach above not work? How could it possibly be causing downstream segfaults? Is GlobalRef documented anywhere? What is a GlobalRef and how I use it?

2 Likes

I don’t know what a GlobalRef is and how it would help your problem. But here are (I think) a few ways to solve your problem:

module Foo
function foo end

# Does not work
macro new_foo_method_v0()
    quote
        $foo(::Val{0}) = 0
    end
end

# Your workaround (IIUC), relying on the module knowing about itself
macro new_foo_method_v1()
    quote
        let M=$Foo
            M.foo(::Val{1}) = 1
        end
    end
end

# The same as above, in a less convoluted way IMO
macro new_foo_method_v2()
    quote
        $Foo.foo(::Val{2}) = 2
    end
end

# Another way, relying only the value of the function (not its name)
macro new_foo_method_v3()
    quote
        (::typeof($foo))(::Val{3}) = 3
    end
end
end

Apart from v0, which fails as you already know, the other versions work as you’d expect (if I understood your problem correctly)

julia> Foo.@new_foo_method_v0
ERROR: syntax: invalid function name "Foo.foo" around REPL[1]:7
Stacktrace:
 [1] top-level scope
   @ REPL[2]:1

julia> Foo.@new_foo_method_v1

julia> Foo.@new_foo_method_v2

julia> Foo.@new_foo_method_v3

julia> methods(Foo.foo)
# 3 methods for generic function "foo":
[1] foo(::Val{1}) in Main at REPL[1]:15
[2] foo(::Val{2}) in Main at REPL[1]:23
[3] foo(::Val{3}) in Main at REPL[1]:30
2 Likes

This is the best way in my opinion.

2 Likes

Thanks! That solves my use case, but I’m still curious about GlobalRefs.

1 Like

As far as I know, GlobalRef is used by the compiler to make variables in local scope refer to objects from the containing global scope. I don’t think there’s too much to “use” here from the high level language, as Simeon mentioned in the issue - they’re emitted by the compiler and need a corresponding mapping in the scope it references.

1 Like