How does the Julia Do Syntax for functions work?

Viewed 846

I've seen here that do syntax can be used as a more convenient way to write map() functions, but I don't understand how e.g. in the example shown in the linked julia docs :

open("outfile", "w") do io
    write(io, data)
end

Which works on the following function definition of 'open'

function open(f::Function, args...)
    io = open(args...)
    try
        f(io)
    finally
        close(io)
    end
end

How in the world does the do pass the io and write(io,data) to the actual function of open and f(io)? Does the function with the do block have to have the same name/be multi-dispatched for the do block to work?

2 Answers

It is a purely syntactical transformation.

func(x) do y
    body
end

is equivalent to

func(y -> body, x)

or

tmp = y -> body
func(tmp, x)

if you will, i.e. the do block defines an anonymous function that is passed as the first argument. For this to work it is, of course, required that there exist a method of func that has a matching signature, for example func(f::Function, ...) = ... (or simplyfunc(f, ...) = ...).


You can inspect what is really happening by using Meta.@lower:

julia> Meta.@lower func(x) do y
           print(y)
       end
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─      $(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─      global var"#1#2"
│        const var"#1#2"
│   %3 = Core._structtype(Main, Symbol("#1#2"), Core.svec(), Core.svec(), false, 0)
│        var"#1#2" = %3
│        Core._setsuper!(var"#1#2", Core.Function)
│        Core._typebody!(var"#1#2", Core.svec())
└──      return nothing
)))
│   %2 = Core.svec(var"#1#2", Core.Any)
│   %3 = Core.svec()
│   %4 = Core.svec(%2, %3, $(QuoteNode(:(#= REPL[1]:2 =#))))
│        $(Expr(:method, false, :(%4), CodeInfo(
    @ REPL[1]:2 within `none'
1 ─ %1 = print(y)
└──      return %1
)))
│        #1 = %new(var"#1#2")
│   %7 = #1
│   %8 = func(%7, x)
└──      return %8
))))

The output is, unfortunately, garbled with implementation details of anonymous functions: The code defines a new struct (%3), makes it callable (%4) using the body of the do block, creates an instance of it (%7), and in the end calls func(%7, x).

If it helps anybody, I have also discovered through fiddling what @fredrikekre has written generally, via this example:

function func(blah::Function,baz,foo)
   blah([baz,foo])
End

and you can call it as either of the following

func("a", "b") do zzz
   vcat(uppercase.(zzz),"C")
end

func(zzz->vcat(uppercase.(zzz),"C"),"a", "b")

both produce the same output

3-element Vector{String}:
 "A"
 "B"
 "C"
Related