It’s also a question of how to build matrices, and how to separate the elements of a row within a matrix. To demonstrate using row vectors:
julia> ([1-2+3], [1 -2+3], [1 -2 +3], [1- 2+ 3])
([2], [1 1], [1 -2 3], [2])
Add a comma and it throws an error:
julia> [1 -2 +3,]
ERROR: syntax: unexpected comma in array expression
similarly,
julia> [1 -2, +3]
ERROR: syntax: missing separator in array expression
This is because commas are used only for building one-dimensional arrays, i.e. column Vector
s, and that’s incompatible with matrices and other multidimensional arrays.
To do arithmetic on elements of the vector, spaces around the operator must be symmetric:
julia> [1-2, +3]
2-element Vector{Int64}:
-1
3
julia> [1 - 2, +3]
2-element Vector{Int64}:
-1
3
(more precisely, if there’s space on the left-side of the operator, then if there is no space on the right-side of the operator it will not be treated as a binary operator but instead as a unary operator.)
To build a higher-dimensional array such as a matrix, use semicolons or newlines:
julia> [1 -2 +3; -4 +5 -6]
2×3 Matrix{Int64}:
1 -2 3
-4 5 -6
In this expression, it becomes perfectly clear why these parsing rules work this way.
You can also build a column vector using semicolons or newlines instead of commas, because column vectors are just the tiniest subset of multi-dimensional arrays:
julia> [1; 2; 3]
3-element Vector{Int64}:
1
2
3
let’s do some arithmetic:
julia> [1+1; 2+2; 3+3]
3-element Vector{Int64}:
2
4
6
But add funny spaces around elements, and you can get an error:
julia> [1+1; 2 +2; 3+3]
ERROR: ArgumentError: argument count does not match specified shape (expected 3, got 4)
Julia’s parser is very clever for this specific reason—within the bounds of []
, it’s assumed that expressions will be space-delimited for matrix-building, and so parsing rules needed to be made to handle this. And then, similar parsing rules are applied when we call macros without parentheses because we’re using spaces again as a separater between arguments.
It’s just something to be mindful of; you’ll run into it sooner or later anyway, either when building arrays or when calling macros, so it’s best to just adopt habits of symmetrical whitespace around operators. Symmetrical whitespace is a good habit anyway for code legibility.
Note that these considerations only apply to plus and minus, which are the only operators which can either be binary or unary; other operators don’t act like this:
julia> ([1/2*3], [1 /2*3], [1 /2 *3], [1/ 2* 3])
([1.5], [1.5], [1.5], [1.5])
Best to adopt good whitespace habits anyway.
Edit:
Note that it could have been decided, instead of having whitespace-dependent rules for when +
and -
would become unary operators, to simply wrap expressions in parentheses within matrices:
julia> [1 (-2) (+3)]
1×3 Matrix{Int64}:
1 -2 3
Then there would be no room for confusion surrounding this topic, neither within arrays nor in macro calls.
However, building large matrices would become a pain. Julia’s creators, being math-centric folks who love matrices, chose to prioritize succinctness in matrix building even though it calls for some context-dependent whitespace-dependent parsing rules.
I’m partial to math myself (after all, math is the closest thing we have to a universal language), so I can’t fault them for that. But it does demonstrate quite nicely how decisions in a language design will necessarily revolve around the priorities of its authors.