Whitespace sensitivity

When is Julia whitespace sensitvie?

  • There are line endings to finish an expression, can be replaced with ;.
  • I recently came across
julia> [randn(2, 3)
                randn(2, 3)]
4×3 Matrix{Float64}:
 -0.0609315  0.342677   -0.173229
 -0.0603575  3.33838     0.229967
  0.894316   1.3177     -2.06582
  2.52888    0.0259237  -1.24774

julia> [randn(2, 3) randn(2, 3)]
2×6 Matrix{Float64}:
  1.40693   0.0107389   0.471341  1.97648    0.69173   -1.1349
 -0.622517  0.268651   -0.78219   0.518276  -0.460273   2.03445

are there more?

4 Likes
julia> [1.0 2.0 3.0 -4.0]
1×4 Matrix{Float64}:
 1.0  2.0  3.0  -4.0

julia> [1.0 2.0 3.0 - 4.0]
1×3 Matrix{Float64}:
 1.0  2.0  -1.0

I run into this from time to time. But it’s not really something different to what you showed already.

julia> [1.0; 2.0; 3.0 - 4.0]
3-element Vector{Float64}:
  1.0
  2.0
 -1.0

julia> [1.0; 2.0; 3.0 -4.0]
ERROR: ArgumentError: argument count does not match specified shape (expected 3, got 4)
Stacktrace:
 [1] hvcat(::Tuple{Int64, Int64, Int64}, ::Float64, ::Vararg{Float64})
   @ Base ./abstractarray.jl:1988
 [2] top-level scope
   @ REPL[9]:1
3 Likes

I am sure there is a lot of stuff you can do with :

1 Like

Macro invocation is also sensitive to whitespace if the expressions are enclosed in parentheses:

https://docs.julialang.org/en/v1/manual/metaprogramming/#Macro-invocation

That’s a standard array syntax like MATLAB and is very useful visually and when copying from a textbook for example. Functions like vcat and hcat do the same thing. The worst whitespace issue in Julia in my opinion still is something like rand (2, 3) is not allowed.

Why? Using a parentheses immediately after the function name seems kinda ubiquitous.

1 Like

this is horrendous and should not be allowed

6 Likes

I don’t have any statistics to assess how ubiquitous this is, but being able to write f ( x ) in any language (at least those I know of) but not in Julia seems weird enough. I saw many code bases that use that without restrictions. It also resembles plain English. Functions in Julia are first class citizens and to my limited knowledge this restriction came from allowing macros to act on tuples. I guess putting restrictions on macros would have been better than putting restrictions on function. Any way, I’m sure that all decisions made by the respectable Julia developers and awesome community are only taken after extensive discussions, so if the community chooses that, it is very welcome.

2 Likes

Could you please elaborate why this is horrendous?

no, but I can point to the fact that all libraries you look, you will find people doing f(x) and PEP8 uses f(x) (if you’re thinking about python, since clearly this is not allowed in Julia)

Many things in Julia are space-sensitive. For example ab is not the same as a b. What I suspect you’re asking about is when horizontal space is used to separate “arguments” and there are two contexts where this happens:

  1. In array concatenation, horizontal whitespace separates horizontally concatenated blocks;
  2. In parens-less macro calls, arguments are separated by horizontal whitespace (cf. macro calls that use parens, which are like function calls and use commas to separate arguments)

The rules for both of these contexts are the same and have the same caveats: 1+2 is the same as 1 + 2 is the same as 1+ 2 but different from 1 +2. You can test it like this:

julia> macro args(exprs...) exprs end
@args (macro with 1 method)

julia> @args 1+2
(:(1 + 2),)

julia> @args 1+ 2
(:(1 + 2),)

julia> @args 1 + 2
(:(1 + 2),)

julia> @args 1 +2
(1, 2)

The same is true for any operator that can be both unary prefix and binary infix.

7 Likes

Opinions about whether this is ugly or improves clarity are besides the point really. Calling a function as f ( x ) was originally allowed in Julia syntax, but it caused a syntax ambiguity in macro calls, so it was disallowed. The ambiguity, if I recall correctly, was that we allow macros to be called either as @m a b or as @m(a, b), both of which are useful in different cases. But then what does @m (a, b) mean? Is it a call with two arguments or a call with a single tuple argument? If you disallow the whitespace between the function and its arguments in function call syntax, then this ambiguity is resolved (it has to be a macro with a single argument that is a tuple).

9 Likes

Yes, that’s totally fine. I’m not sure if the macros issue could’ve been solved using Tuple(..)?

That’s certainly a way to indicate that you want to pass a tuple, but it’s a different syntax which does matter for macros, and it doesn’t help disambiguate the other syntax.

Wow. I hadn’t come across this one [1.0; 2.0; 3.0 -4.0] yet, I’ll keep it in mind!

1 Like

Ouch. Would anyone miss it if this were disallowed (note, doesn’t strictly have to disallow for Vectors, only for 1×N matrices, but probably best to do for all cases for consistency)? It doesn’t seem super useful, and only allowing:

julia> [1.0 2.0 (3.0 - 4.0)]

seems would be ok. In practice (for arrays) how often would you actually do something like this, on purpose, or at least where a - b are variables (or if either is not a literal)?

At least accident-waiting-to-happen (a bug?) is something a linter, could and should (do they) catch. If not considered a bug, will have to wait for Julia 2.0… until then, I’ve been thinking Julia needs a debug/warning mode, and if running in the REPL, it seems it could be on by default.

We wouldn’t be able to write the following Matrix of Any:

v = [sin (2,3)]

It would error as [sin(2,3)]

3 Likes

Banning that doesn’t seem “horrendous”. :slight_smile: At least for sin.

There is no problem with arrays as they have their special syntax anyways as I said above and as mentioned also by @StefanKarpinski . The problem dates back to 2014 when they wanted to allow @m (a, b) to mean calling the macro @m on a tuple. In my opinion, they chose the easiest solution not the best solution in that case. Functions are used more than anything else in Julia, but macros should only be second to them. So, I imagine the better solution was to choose (and I can’t assess how difficult this is TBH):

@m a, b   # call @m on two variables a, b
@m a b    # call @m on two variables a, b
@m (a, b) # call @m on single variable (a, b)

and leave functions alone. That’s, force a space after macro names because macros are equivalent to functions without the parentheses; f(x, y) should be equivalent to @f x, y.
Allowing spaces after function names, however, can significantly improve readability in many cases. the GNU Coding Standards recommends this (“We find it easier to read a program when it has spaces before the open-parentheses and after the commas”). A similar case in Julia is when they forced a space before ? and after : in 1 > 2 ? 1 : 0. In this case, 1 > 2? 1 : 0 is like normal English and is not bad at all. Many people also, including myself, have the habit of adding that space before function names for clarity (I use it a lot in my introductory C course). In general, languages that give freedom and trust to the programmer seem to be more enjoyable (Nim was criticized at some point for relying heavily on spaces). Some emerging languages even went farther and removed macros altogether, but I think macros are pretty useful though.

If you’re writing Lisp, sure. I mean in that case, (f (2, 3))means f((2, 3)) right? Postfix, I would add space in Lisp too because otherwise I would think I’m calling the function with two arguments

1 Like