Programmatic goto

For probabilistic programming work, I was exploring the idea of adding @goto and @label statements in the code for a model, and then calling to an outside “driver” function to see where to go next. Prototyping this idea, I need the driver to return a Command, with Goto as one option. So something like

abstract type Command end

struct Goto <: Command
    target::Symbol
end

Since @goto is implemented as

macro goto(name::Symbol)
    return esc(Expr(:symbolicgoto, name))
end

I thought I could do something like

macro maygoto(ex)
    quote
        command = $ex
        result = if command isa Goto
            target = command.target
            esc(Expr(:symbolicgoto, target))
        else
            nothing
        end
        result
    end
end

But a test run doesn’t work:

julia> function f()
           println("start")
           @maygoto Goto(:blah)
           println("skip this")
           @label blah
           println("done")
       end
f (generic function with 1 method)

julia> f()
start
skip this
done

Am I making a silly mistake here? Or maybe just a very serious mistake? Help please :slight_smile:

I don’t think you can truly test for if command is a Goto inside macro since type is a run-time concept but macro only sees source code:

julia> macro maygoto(ex)
           command = ex.args[1]
           if command == :Goto
               return esc(Expr(:symbolicgoto, ex.args[2].value))
           else
               return nothing
           end
       end
@maygoto (macro with 1 method)

julia> function f()
           println("start")
           @maygoto Goto(:blah)
           println("skip this")
           @label blah
           println("done")
       end
f (generic function with 1 method)

julia> f()
start
done

your original macro is doing the technically correctthing as far as isa Goto concerns:


julia> @macroexpand function f()
                  println("start")
                  @maygoto Goto(:blah)
                  println("skip this")
                  @label blah
                  println("done")
              end
:(function f()
      #= REPL[4]:1 =#
      #= REPL[4]:2 =#
      println("start")
      #= REPL[4]:3 =#
      begin
          #= REPL[3]:3 =#
          var"#16#command" = Main.Goto(:blah)
          #= REPL[3]:4 =#
          var"#17#result" = if var"#16#command" isa Main.Goto
                  #= REPL[3]:5 =#
                  var"#18#target" = (var"#16#command").target
                  #= REPL[3]:6 =#
                  Main.esc(Main.Expr(:symbolicgoto, var"#18#target"))
              else
                  #= REPL[3]:8 =#
                  Main.nothing
              end
          #= REPL[3]:10 =#
          var"#17#result"
      end
      #= REPL[4]:4 =#
      println("skip this")
      #= REPL[4]:5 =#
      $(Expr(:symboliclabel, :blah))
      #= REPL[4]:6 =#
      println("done")
  end)
1 Like

Right, but the test is in the quote, so

julia> macro maygoto(ex)
           quote
               command = $ex
               result = if command isa Goto
                   println("It's a goto!")
                   target = command.target
                   esc(Expr(:symbolicgoto, target))
               else
                   nothing
               end
               result
           end
       end
@maygoto (macro with 1 method)

julia> function f()
           println("start")
           target = :blah
           @maygoto Goto(:blah)
           println("skip this")
           @label blah
           println("done")
       end
f (generic function with 1 method)

julia> f()
start
It's a goto!
skip this
done

Things like this are always weird:

julia> @macroexpand @goto blah
:($(Expr(:symbolicgoto, :blah)))

Is there even a way to write that directly in code, without the macro?

YOU CAN MACROEXPAND A FUNCTION?!?!

I had no idea, that’s really nice :smiley:

right, thus the whole quote end is the result of expansion and you get a perfectly correct isa Goto at run-time but the returned Expr() is “useless” now that it’s run-time

haha, isn’t that nice?

1 Like

MacroTools.@macroexpand(@mymacro ...) |> MacroTools.prettify is really really nice.

1 Like

When I was learning macros, someone pointed out that they don’t give you any new capabilities, but just let you write things with fewer keystrokes. That made a lot of sense, and really helped me get more comfortable with them.

But things like this still seem magical:

julia> @macroexpand @goto blah
:($(Expr(:symbolicgoto, :blah)))

I know there are some “magical” macros, but for @goto, the code is right there in Julia.

So… is that only mostly right? Is there no way to write @goto blah inline without using a macro?

I mean, I want to use a macro, but I think I first need to understand what’s actually going on here.

goto is a really low-level thing if you think about it, it literally marks a location to jump to on stack. So the reason it’s a macro is that it simply leaves an expression for the compiler to figure out what is the “location” on the stack to jump to once it compiles the function into binary (before then, you can only talk about “label” because you don’t know how much big the binary blob of function ends up becoming).

Looking back, even in C++ the label/goto has weird syntax, because they kind of stay location markers until the last step (asm):

label:
    ...

goto label;
1 Like

Good point. I guess this would need to be at the IR level - for higher-level code I’d probably need to go with continuations or something similar