Why begin/end instead of { } for block delimiter

Why does Julia use begin/end as the block delimiter instead of the more common and concise { }? Why is that better?

{} is used for types.

Also end can end any of let, for, while, if, function, struct, module etc. so it means an opening { can be skipped. After all, itā€™s just for ... end, not for begin ... end

3 Likes

That means built-in blocks are easier than user-defined blocks, which I consider bad not good. Still {} is shorter than end anyway.

Unfortunately, Julia is not at that stage of development anymore. I hope this minor syntax choice will not be a blocker for you to explore the language.

2 Likes

At this point Iā€™m just looking for an explanation of why it is this way.

1 Like

Parameterized types are used heavily in Julia, and are often deeply nested ala Dict{Int,Vector{Complex{Float64}}}, so a compact (single-character bracket) ASCII symbol was crucial, and there arenā€™t many to choose from. (C++ was forced to choose <...> and itā€™s a mess for nesting because < and << are also operators.)

begin/end blocks are used much less densely ā€” you rarely have multiple nested blocks on a single line! ā€” and anyway we actually do have a shorthand (a; b; c) for begin a; b; c; end. So, the preference was for a slightly more verbose, human-readable syntax.

There is also the fact that Juliaā€™s surface syntax and keywords were (very loosely) influenced by Matlab, among other influences.

(In the early days of Julia, we also had regular influxes of Python programmers asking why we didnā€™t use indentation for blocks, which ā€œeveryone knowsā€ is vastly superior.)

34 Likes

Thanks. I think something is missing from this explanation though.

Block delimiter syntax doesnā€™t conflict with postfix ā€” after all, () is used for both currently: f() and (a; b). So {a; b} would be okay.

However () is used much more densely than the others, namely in function calls and tuples. Moreover, (a; b) conflicts with what could have been a very nice syntax for the FrankenTuple argument/parameter data structure.

This seems a likely explanation given Matlabā€™s end syntax (though I donā€™t see a begin there).

2 Likes

I donā€™t recall seeing claims that this is better, it is merely different. I canā€™t say whether the devs were influenced by Python, but thatā€™s another language that has rethought syntax and freed up curly braces for other things. If you stick around long enough in computing, youā€™ll see a variety of approaches to syntax, and which is ā€œbetterā€ is difficult to resolve, and subject to change over time.

Personally, I like Juliaā€™s syntax and currently prefer it over C style. But I prefer C over Pascal, not necessarily for any defensible reason. I can live both with and without {} blocks.

2 Likes

Thereā€™s another thing I forgot: in the early days of Julia, {a; b; c} and {a, b, c} were syntax for what is now Any[a; b; c] or Any[a, b, c], e.g. in Julia 0.3:

julia> {3,4,5}
3-element Array{Any,1}:
 3
 4
 5

julia> {3;4;5}
3-element Array{Any,1}:
 3
 4
 5

This was directly taken from Matlabā€™s cell array syntax. In Julia 0.4, this was deprecated in favor of Any[...] for literal arrays of arbitrary-type elements (which arenā€™t used that often anyway). As you say, this could have freed up the {a; b; c} syntax for blocks, but it would have been a big upheaval at that point, with not a huge benefit.

Now, we are free to to use {a; b; c} for a really cool new feature (if we can think of one) without breaking anything. Itā€™s already available for people to use in macros for domain-specific languages because it parses:

julia> ex = Meta.parse("{a;b;c}")
:({a; b; c})

julia> dump(ex)
Expr
  head: Symbol bracescat
  args: Array{Any}((3,))
    1: Symbol a
    2: Symbol b
    3: Symbol c
14 Likes

See also:

5 Likes

If you want, you can use braces as block in macros.

macro b(expr)
    expr.head == :bracescat || error("invalid block syntax, must write @b {...}")
    expr.head = :block
    esc(expr)
end

@b {
    x = 1 + 1
    y = 3 + 4
    x + y
}
function in_to_eq(ex::Expr)
    if ex.head == :call && ex.args[1] āˆˆ (:āˆˆ, :in)
        Expr(:(=), ex.args[2:end]...)
    else
        ex
    end
end
@eval macro $(Symbol("for"))(spec, body)
    if body.head == :bracescat || body.head == :braces
        body.head = :block
    end
    specā€² = if spec.head == :block
        Expr(:block, in_to_eq.(spec.args)...)
    else
        in_to_eq(spec)
    end
    esc(Expr(:for, specā€², body))
end
@for x āˆˆ 1:3 {
    @show x
}
#+end_src

#+RESULTS:
: x = 1
: x = 2
: x = 3
5 Likes

Ruby and Lua both have end blocks btw, so itā€™s hardly a weird choice for Julia

5 Likes

oooh juicy! Is there a [modern] thread to bounce ideas for {...}?

I suspect block delimiter is the best possible meaning. () is so overloaded as to be unusable and @mymacro {} is so concise and flexible that any desired meaning would fit well in that format.

we already have begin and end for block delimiting though; feels like there must be a more interesting use floating out thereā€¦

I like Juliaā€™s use of ()ā€”it works unambiguously despite being used for all manner of things: evaluation precedence, function call, tuple, named tuple, and generator.

And then [] is pretty well-utilized for vectors, matrices, and the whole gamut of other arrays, as well as indexing.

Maybe Dict? Itā€™s a built-in type that has no dedicated syntax. Maybe it deserves something? JavaScript uses {} for dynamic objects that organize into tree structures, which behave in many ways similarly to Dict, so this might make JavaScripters happy. Or maybe a PropDict (from PropDicts.jl) would be a bit nicer, with {a=1, b=2} being syntax sugar for PropDict(:a=>1, :b=>2); then JSON could almost be plugged in directly as Julia code.

Or, maybe Set? Itā€™s tempting that set theory already uses curly braces for sets and for set builder notation, so maybe {item1, item2, item3} for Set(item1, item2, item3). I wonder if itā€™d be interesting to make lazily evaluated (possibly infinite) sets, e.g. evensquares = {x where y isa Int && x = y^2}. I have zero insight into programmatic set manipulation, Iā€™m just spitballing.

Or, for the functional language aficionados, maybe use it for the lazily evaluated List object from Lazy.jlā€¦ maybe {1, 2, 3, ...} for a list? (and assume itā€™s an arithmetic or geometric sequence, perhaps)

Or, considering that curly braces are already heavily used in Juliaā€™s type system, perhaps thereā€™s some sort of ā€œtype arithmeticā€ that can be doneā€¦ Iā€™m still trying to figure out how to subtract a type, hah.

Or, maybe our machine learning friends can offer some ideas. Maybe it could be used as a data structure for neural nets, or maybe graphs more generally, who knows.

All Iā€™m saying is, using {...} as a stand-in for begin...end seems wasteful. {} are such generally useful characters, to burn them on something that already has syntax would be a shame. (I remember as a kid how weird it felt to learn {} for block delimiting but eventually with practice coming to love it, and then many years later learning MatLab and feeling ā€œhey, end feels fine too!ā€)

9 Likes

Type arithmetic would be awesome! For statically sized objects especially, calculating the number of chunks needed in a static bit array, determining the number of arguments to a tuple parameterā€”Being able to define math inside type inference could maybe reduce the complexity of some of the more out of control parametric types (see the DifferentialEquations error messages) by appending compile-time information to a typeā€¦
maybe like

struct sbitarray{N, CType; nchunks} where {nchunks = cld(N,sizeof(CType)*8)}
   chunks::Tuple{Varargs{Ctype,nchunks}} 
end

but then you only see sbitarray{N, CType} for printing and the type tree (independent type variables vs. dependent type variables)

2 Likes

Iā€™m not sure Cā€™s { } notation is actually shorter in practice:

C:     if (a < b) { c = a; a = b }
Julia: if a < b; c = a; a = b; end
1 Like

but you contrived that, it is 2 chars more!

if(x){y}
vs
if x;y;end

See this issue for previous discussions and why that is not easy/already happens with constant propagation

In particular,

extending type inference from the user-side means that just loading a package can potentially mean having to recompile the compiler and also potentially invalidate all code that was compiled with that compiler. This is exactly counter to the recent push to reduce invalidations.

Julia will not move to dependent typing in the foreseeable future.

Itā€™s not contrived since:

if(x){y}
vs.
x && y
vs. [not needed]
if x;y;end