Writting long functions with the short function definition syntax. Equivalent? Why not?

Hi! First, since I’m new let me say I’ve been wanting to use julia for a some time and finally getting into it. Enjoying the language tremendously, thanks for all the great work.

There’s a way of writting long functions which I don’t see used, and I wonder why. Here it is:

foo(x, y, z) = begin
    r = 0
    # ... compute stuff ...
    r
end

instead of the standard

function foo(x, y, z)
    r = 0
    # ... compute stuff ...
    r
end

Are these equivalent? Almost all julia code I’ve seen uses the function keyword for long functions, or foo(x, y, z) = (r = 0; ...; r) if it fits in one line.

I know it is stupid to fight a language’s syntax when you first start using it and I’ve got nothing against the function syntax, it does look better. The only thing going for the begin syntax is that the transition from short to long functions is more seamless, I think. Also, code written in this way has all the function names aligned to the first column which can be easier to scan.

2 Likes

According to Functions · The Julia Language they are equivalent beside that the short form can only consists of one expression.
For me, the first one greatly enhances readability for short, math like functions.
Btw, welcome to the forum.

1 Like

I think the reason you don’t see this used in the wild is that you’re using the assignment form syntax for a multiline definition, but I think most Julia users believe the value of assignment form is that it’s optimized for single line definitions. I don’t have data offhand, but would guess >80% of assignment form usage is single line definitions. For example, the first few results from egrep '\w+\([^\)]*\) = ' * in julia/base are:

Base.jl:getproperty(x::Type, f::Symbol) = (@inline; getfield(x, f))
Base.jl:getproperty(x::Tuple, f::Int) = (@inline; getfield(x, f))
Base.jl:getproperty(x, f::Symbol) = (@inline; getfield(x, f))
Base.jl:dotgetproperty(x, f) = getproperty(x, f)
Base.jl:getproperty(x::Module, f::Symbol, order::Symbol) = (@inline; getglobal(x, f, order))
Base.jl:    val::Core.get_binding_type(x, f) = v
Base.jl:getproperty(x::Type, f::Symbol, order::Symbol) = (@inline; getfield(x, f, order))
Base.jl:getproperty(x::Tuple, f::Int, order::Symbol) = (@inline; getfield(x, f, order))
Base.jl:getproperty(x, f::Symbol, order::Symbol) = (@inline; getfield(x, f, order))
Base.jl:convert(::Type{Any}, Core.@nospecialize x) = x
Base.jl:eval(x) = Core.eval(Base, x)
Base.jl:eval(m::Module, x) = Core.eval(m, x)
Base.jl:    show(io::IO, x) = Core.show(io, x)
Base.jl:    print(io::IO, a...) = Core.print(io, a...)
Base.jl:    println(io::IO, x...) = Core.println(io, x...)
Base.jl:time_ns() = ccall(:jl_hrtime, UInt64, ())
Base.jl:sizeof(s::String) = Core.sizeof(s)  # needed by gensym as called from simdloop
Base.jl:a_method_to_overwrite_in_test() = inferencebarrier(1)
Base.jl:include(mod::Module, _path::AbstractString) = _include(identity, mod, _path)
Base.jl:include(mapexpr::Function, mod::Module, _path::AbstractString) = _include(mapexpr, mod, _path)
Base.jl:in_sysimage(pkgid::PkgId) = pkgid in _sysimage_modules
Enums.jl:Base.Symbol(x::Enum) = namemap(typeof(x))[Integer(x)]::Symbol
1 Like

If you want to confirm that the result is truly the same, you can write the same function in both those ways, then run them to see if the generated code is the same with @code_lowered foo(x, y, z) , @code_lowered foo2(x, y, z) and see if the output is the same. I tried this for a simple example and both produced identical code, so it does indeed seem like just a style difference.

2 Likes

Good, they are equivalent, thanks.

Then… What do you think of this style, and of the points about ergonomics? That

the transition from short to long functions is more seamless, I think. Also, code written in this way has all the function names aligned to the first column which can be easier to scan.

This doesn’t have a definite answer, I know.

1 Like

Personally, I like the look of the function foo(x, y, z) style. One additional consideration for style is if there is a readability difference when you start using things like parametric types. But I think as long as a coding style is consistent, then to each their own.

FWIW, there are some recommendations for style that may be of interest. Is there a Julia Style Guide?

For long (multi-line) functions, setting them apart with function ... end makes it easier to tell where they start and where they stop.

Personally, I dislike the foo() = begin ... end style, and use the “short form syntax” only for very short functions.

3 Likes

For the short form, there are some problems with unexpected parsing, which makes them (sometimes) less flexible to use. See for example Error in: f(x::T)::Int where T = x · Issue #21847 · JuliaLang/julia · GitHub.

Right, I already got bit by that one. Thanks. Needing to use parenthesis does break the homogeneity of the notation in that case.