A most harrowing collection of Julia WATs

Yes they are just functions that return expressions which are spliced into the AST. Dispatch works but only with literal types, not types of arguments at runtime.

1 Like

That makes sense as to why I only got it to “work” with Int’s, Float’s, String, Symbol’s and Expr’s. Thanks. :slight_smile:

This is sort of obvious, but still tricky:

julia> r = typemin(Int):typemax(Int)
-9223372036854775808:9223372036854775807

julia> length(r)
0

julia> r[2] # WAT
-9223372036854775807

Related: https://github.com/JuliaLang/julia/issues/45389

14 Likes

Not sure whether this is a “WAT” but it seems that adding an integer to an integer range just shifts the lower/upper limit of a range:

julia>  length(1:7 .+ 4)
11
julia>  length(4 .+ 1:7)
3

I would have expected a shift of all values (hence keeping the length of the range to 7). This, however, requires extra set of brackets:

julia>  length((1:7) .+ 4)
7

Or is is simply the case that .+ has priority over :?

Also, it seems that line breaks have an influence on array construction. Without line breaks:

julia> size([1 2 3; 4 5 6])
(2, 3)

With line breaks:

julia> size([1
       2
       3;
       4
       5
       6])
(6,)
3 Likes

Yes. The colon has a rather low priority and I can never remember the details, I recommend always using parentheses in non-trivial expressions, to be safe

11 Likes

I recall reading somewhere recently that a line break is equivalent to a ; thus

julia> a = [1 2 3
       4 5 6]
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia>

2 Likes

That’s a feature. Arrays in Julia are space sensitive similar to MATLAB (except comma is not equivalent to a space in Julia). This is not a bad thing, it makes arrays and matrices look similar to what you actually see in a book. So, [1 2 3] is not equal to [1, 2, 3], the former is a single-row matrix but the latter is a flat vector and is equivalent to adding line breaks. Also, [-1 +2] is not equal to [- 1 + 2]. People having familiarity with MATLAB find this behavior very natural.

4 Likes

I am familiar with matlab, but that makes me a little uneasy all the same. It’s better than the alternative, don’t get me wrong, but I’ve definitely had whitespace errors in constructions like that before.

About 1:10 + 0.5, I put this in my vimrc because endpoint arithmetic is so dangerous it should be an error.

" Warn about 1:10 + 0.5
syn match juliaRangeArithemticWarning "(\@<![^(,; ]\+:[^(,; ]\+\s*\.\?[+\-/*^]"
hi def link juliaRangeArithemticWarning ErrorMsg
9 Likes

About 1:10 + 0.5

agreed. that’s a bad one

1 Like

So you never do myvec[1:end-1] or similar?

4 Likes

I try to remember to parenthesize it.

I use UnitRanges for integer expressions only. Otherwise range(). I believe this has saved me some trouble.

idk how much of a WAT this is, but an infrequent but regular typo of mine is mistaking !== to be the negation of ==, when it should be != : == :: !== : ===. It’s because typing ! in front of an expression (or a method) usually does boolean negation:

julia> !in(3, [3])
false

julia> !(in(3, [3]))
false

julia> !==([3], [3])
true

julia> !(==([3], [3]))
false
8 Likes

I think matrices are special any way, and copying their behavior from MATLAB is not bad at all, the best thing MATLAB does is matrices and that is what its name suggests. And for 1:10 + 0.5, I don’t see it that confusing, I read it like 1:end-1, and again it’s similar to what I used to do in MATLAB for years before converting to Julia, so no effort at all for me.

The things that are really WATs for me and I see them bad at least, are things like this (I know the reason behind them, but I don’t think they are intuitive at all):

2.^(1:5), 2./(1:5), .. # why should I ever write a float literal as 2.?
x = 1 > 2? 5:6
x = 1 > 2? 5: 6
x = 1 > 2? 5 : 6
x = 1 > 2 ? 5:6
fname ( some_variable )  

all the above are wrong in Julia for no obvious reason, of course the list is long but I only give small examples. It makes writing Julia code an uneasy experience when the user is interrupted just because adding a small space is required at this point. Languages like Python tries to be as nice as possible with the user and tries it’s best to make their code run, unless there is a big mistake. No wonder why it has all that popularity especially as a first language.

To give some comments why I see the above list unintuitive, consider for example 2.^(1:5). The natural thinking here says that this is a broadcasting. If a want a float literal, I should be explicit and write it as 2.0. In some modern languages like V, they explicitly warn against this. Even in scientific writing guides that we teach students, they advise against writing a float, like 2., without a trailing zero. So, the natural thing is that 2 is an Int and the dot is for broadcasting, which also goes well with languages that use dot broadcasting like MATLAB (even if it uses floats by default).

For the ternary operator examples, why should Julia enforce me to add spaces at all when it doesn’t do any harm. I feel removing the space before ? is natural like normal English. And there is no reason to enforce any spaces, it should be left to the code writer, consistency here means nothing. For fname ( some_variable ), nothing should be wrong here. Again, freedom is valuable, I expect to copy-paste some code lines and it just works, regardless of that tiny space here and there. Also @macros should never have higher priorities than functions, so if the space ruins macros, fix macros and not the innocent space. That makes Julia more pleasant to work with and more user friendly.

I might mention some other small imperfections like long-name identifiers. Why eachindex, eachrow, eachcol, eachslice, CartesianIndices, … and not simply indices, rows, cols, slices, etc.? The prefix each here is surplus and is naturally obvious from the final s. We already have axes and Indices in LoopVectorization, why not adopt shorter but expressive names?

Other things that come to mind, like Matrix{Float64}(undef, m, n). Why not Matrix(m,n) like rand(m,n)? and if Int is required Matrix(Int,m,n). For many Julia new users the latter is very welcome. If some one says because of consistency with that constructor in that module of that … in the Mars planet, I would say too much consistency is killer, we are humans, not consistent machines. I want to replace all our MATLAB dependence in my university with Julia, but I want to make a good experience for the students and save my self a lot of headache. Although Julia represents the language I dreamed of for years, I can’t currently fully adopt it for my undergrads.

2 Likes

This is a “feature” that I have the impression most long-time developers have come to hate. It is much better to have the language to throw an error because of ambiguous syntax than chose one of the possible alternatives and run with it.

I do think that allowing 2x and 2. syntax (for multiplication and floating-point literals, respectively) was a mistake. However, since we do have it, the right thing to do is to treat ambiguous cases like they are ambiguous, and not create special cases in which “oh, here we will assume that interpretation because you have an alternative unambiguous way to express the other case”.

Nothing of this warrants any WATs. These are nitpicks and probably should not be in this thread. I suggest you create a new thread to discuss these topics, or open PRs to see if you can convince the Core developers to adopt the changes

9 Likes

But then [fname ( some_variable )] would be ambiguous (might be a minor point, but still).

EDIT: Another ambiguity: @macro fname ( some_variable )

Not sure what you mean here. I don’t think how macros are parsed influences how function calls are parsed.

EDIT: Except I guess in the ambiguity mentioned above.

There’s a paradox going on here. On one hand, it is flexible to let users write 2. to mean 2.0, or 2x to mean 2*x, in other words the parser “tries to be as nice as possible with the user and tries it’s best to make their code run, unless there is a big mistake.” However, the syntax is flexible in so many places they start overlapping, causing unerrorable typos we must avoid with practiced inflexibility.

In the ? : case, it makes sense to enforce spaces because : is used for so many things; even the properly spaced 5 : 6 makes me think of UnitRange. Enforcing spaces sometimes isn’t so bad, even Python requires spaces for some expressions (especially indentation).

Personally, I’m of the opposite opinion in general, some rigidity makes things easier to follow (Python’s “one obvious way to do it”). I really avoid ? : and stick to if else. The operator typo x-y = x+y was mentioned before, but I don’t even like f(x, y) = x+y or (x, y) -> x+y. I have noticed multiple people, especially when they run into functors for the first time, confuse = methods for “weird” assignment. I think code would be easier to read and learn if all methods were written in 1 obvious way like function (x, y) x+y end, which is valid syntax now.

If function end is too long to type out every time, which seems to be why people reach for the = and -> syntax, v2.0 could shorten it to fn } (Fn is familiar as a key, and block keywords can be treated as implicit {) or repurpose -> (but not both). An explicit symbol to end blocks is nice because of this WAT:

julia> function (x) x, 2 end
#1 (generic function with 1 method)

julia> (x) -> x, 2
(var"#3#4"(), 2)

julia> (x) -> (x, 2)
#5 (generic function with 1 method)
4 Likes

Strong agree on this. The multiplicative juxtaposition is actually nice, but it also leads to issues. Instead, I don’t like 2. at all, never liked in any other languages.

9 Likes

whitespace is important

Most of these complaints are just people who apparently hate pressing the spacebar

3 Likes

Okay here’s a good one from this thread a bit related to a WAT documented in the OP blog and noticed by the same @jakobnissen! I’ll condense it here:

julia> @which sum(i for i in 1:3; init=0)
(::Base.var"#sum##kw")(::Any, ::typeof(sum), a) in Base at reduce.jl:532

julia> @which sum(i for i in 1:3, init=0)
sum(a; kw...) in Base at reduce.jl:532

julia> @which sum((i for i in 1:3), init=0)
(::Base.var"#sum##kw")(::Any, ::typeof(sum), a) in Base at reduce.jl:532

In the middle case, the iterator is not (i for i in 1:3), it’s (i for i in 1:3, init in 0) because in is lowered to = (we use that symbol for so many things), and the typo is silent because it’s valid to iterate over many scalars. Always remember to parenthesize your Generator comprehensions if they’re not the only argument in a call!

9 Likes