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?
Functions in Julia can be combined by composing or piping (chaining) them together. Function composition is when you combine functions together and apply the resulting composition to arguments. You use the function composition operator ( ∘ ) to compose the functions, so (f ∘ g)(args...) is the same as f(g(args...)) .
Function is an abstract type. So for example Vector{Function} is like a Vector{Any} , or Vector{Integer} : Julia just can't infer the results.
'return' keyword in Julia is used to return the last computed value to the caller function. This keyword will cause the enclosing function to exit once the value is returned. return keyword will make the function to exit immediately and the expressions after the return statement will not be executed.
Method Tables Every function in Julia is a generic function. A generic function is conceptually a single function, but consists of many definitions, or methods. The methods of a generic function are stored in a method table. Method tables (type MethodTable ) are associated with TypeName s.
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"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With