`depart` for `return`, a syntactic sugar for blocks with early-stop

Julia has let block in case you need a local scope, so you don’t often need ad-hoc defined+called anonymous function for that as in JavaScript. But another sugar in the ad-hoc called function syntax is, you can early-return inside that block.

I’m not sure this is my new discovery in Julia, though the approach is rather simple.

function depart(f, args...) f(args...) end

Then for lengthy blocks those don’t deserve a dedicated function name:

m, n = 2, -7
#...

depart(3n, m) do x, y

  if x > 10
    println("Big x=$x vs $y")
    return
  end

  if x < 0
    println("Negative x=$x vs $y")
    return
  end

  @assert false "Not that anyway expected."

end

I like it quite much, and I hope it be appropriate Julia practice, what’s your thoughts?

1 Like

I’ve definitely wished on few occasions to be able to “break out” of the current let (or similar) block. But break can’t be used for that in a non-breaking julia change, and I’d guess the usefulness of this is too marginal to introduce a new keyword for it. Your trick with depart is cool, although I would probably not bother in general, and instead just use if/else blocks, or even @goto end_of_block if it’s more practical.

Doesn’t map work the same way?

julia> m, n = 2, -7
(2, -7)

julia> map(3n,m) do x, y
             if x > 10
           println("Big x=$x vs $y")
           return
         end

         if x < 0
           println("Negative x=$x vs $y")
           return
         end

         @assert false "Not that anyway expected."

       end
Negative x=-21 vs 2

Yes, someone at r/Julia pointed that too: https://www.reddit.com/r/Julia/comments/ss7jel/comment/hwxer6n/?utm_source=share&utm_medium=web2x&context=3

But map would collect the results into a vector, which is bloating if you don’t need that. Also depart seem to express the intention clearer, i.e. marking a return point for nested returns.

1 Like

This is pretty neat. I like it.

I’ve definitely found myself running into needing something like this every now and again.

I think it can probably be simplified slightly: depart(f) = f() and then simply

depart() do
    # capture variables through closure
end

e.g.

x = 2
depart() do
    if x > 10
        ...
    end
end

That particular aspect can be solved by replacing map with foreach. But of course there are other differences.

Nice idea. Regarding the name, I find depart a bit misleading - it enables departing, doesn’t perform a depart itself. Some names that occur off the top of my head are:

  • departable(3n, m) do ... - seems fine; I would prefer the return keyword was more directly referred to somehow, but one can get used to this; maybe someone unfamiliar could read it as something to do with tables, though that also seems a minor first-use-only issue.
  • exitable(3n, m) - I can’t decide if this sounds cool or sounds weird. Leaning towards weird tbh.
  • withreturn(3n, m) - I like that it explicitly mentions the return which makes the point of the block immediately clear. That makes me slightly prefer this over departable
  • returnable(3n, m) - this makes it sound like this is something to be returned, not something in which return happens
  • breakable(3n, m) - this is like departable, but uses a more familiar keyword, and one can quickly learn to see returns within the block as breaks
  • For those that don’t mind verbose names, breakableblock is a good name too.

(One could turn either of the last two into a macro and have it accept actual break statements, and replace those with return statements in the macro - but let’s keep it simple here.)

Oh, and for those familiar with Lisp-y languages, apply is a nice name too. For those with that context, the returns will have obvious meaning.

PS: On that note, I find it surprising that Julia doesn’t have a apply/call function or statement (unless I’m forgetting something?). Neither invoke nor invokelatest is directly equivalent to a normal function call in all situations. One can do this using the non-allocating Iterators.map and only, but it’s clunky (in terms of syntax):


julia> Iterators.map(3n, m)  do x, y
                if x > 10
                  println("Big x=$x vs $y")
                  return
                end
       
                if x < 0
                  println("Negative x=$x vs $y")
                  return
                end
       
                @assert false "Not that anyway expected."
       
              end |> only
Negative x=-21 vs 2