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