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
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.
At this point I’m just looking for an explanation of why it is this way.
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.)
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).
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.
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
See also:
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
Ruby and Lua both have end
blocks btw, so it’s hardly a weird choice for Julia
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!”)
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)
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
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