A most harrowing collection of Julia WATs

for x in … introduces a new x regardless of whether x is already defined:

The text is inaccurate but not entirely false.

julia> let
       x = 2
       for outer x = 1:5
       end
       @show x
       end
x = 5

The keyword outer is necessary to use an already defined x, however, it cannot be in global scope (i.e., the variable must be from local scope).

This is a beautiful abomination :joy:

It took me a little while to figure out why this even parses or what it means. Here’s what’s going on:

  1. T where T is just a confusing way of writing Any
  2. where is a contextual keyword, so the T may be replace with where to get where where where
  3. x isa Any for all values x
  4. In particular, isa isa Any
  5. Putting that together gives us the beautiful isa isa where where where as another name for true.
49 Likes

Huh, never would have guessed:

julia> where = "I can assign a value to `where`."
"I can assign a value to `where`."

Looks like all syntax highlighters need to update their rules :stuck_out_tongue_winking_eye:

3 Likes

In general the Julia parser tries not to steal identifiers as keywords so there’s several words (abstract, as, doc, mutable, outer, primitive, type, var, isa, where) which are normally identifiers but have special parsing rules in some circumstances.

There’s two circumstance (quoting with : and field access with .) where words which are normally keywords become identifiers:

x.end = :for

A downside of this is that a syntax highlighter would need to be a full parser to get the highlighting 100% correct.

9 Likes

Unless you overload getproperty, there doesn’t appear to be many situations where you can actually get a property named end:

julia> struct A
           end

julia> (a=1, end=2)
ERROR: syntax: unexpected "end"

In the first example, the REPL finished the struct definition before I could add another end. :joy:

VS Code does fine with highlighting symbols:

julia> struct A
           var"end"
       end

julia> A(1).end
1
10 Likes

Ha, ok. In that case I guess I’m obligated to report that VS Code does not get the highlighting for end correct:

image

1 Like

Yeah the rules are (mostly?) local enough that syntax highlighting should be able to get them right though it’ll need some lookahead / lookbehind for things like outer which are somewhat more annoying. outer is a keyword here: for outer i = 1:10 and here for outer i (in) 1:10 but not here: for outer = 1:10 or here for outer in 1:10.

1 Like

Speaking of outer… here’s a puzzle I’ve concocted using some other tricks up-thread, and another fairly serious WAT I recently discovered.

Does anything get printed here?

let isa = isa isa where where where
    outer = false
    for outer outer in isa
    end
    try
        if outer isa Any
            @info "Certainly true, right?"
        end
    finally
        outer = false
    catch
        if outer
            @info "Surprise?"
        end
    end
end

(Edit: I think it’s fair to say this isn’t exactly a WAT in itself, as it’s intentionally obtuse :laughing: )

5 Likes

Neither corner case nor rare specimen.

println("What a lovely day!")
for i in 1 : 100_000_000
    for j in 1 : 10_000
        x = cos(j)
    end
end
# Next day...
println("WAT!")); # Syntax error discovered
3 Likes

For me the actual WAT is writing code like this :grinning: Still fun to see of course.

Even more baffling is reading this post, trying to figure out what’s going on and getting this help message.

               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.7.2 (2022-02-06)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

help?> outer
search: count_zeros RoundToZero OutOfMemoryError ConcurrencyViolationError countlines count_ones RoundNearestTiesUp RoundNearestTiesAway

Couldn't find outer
Perhaps you meant one, ones, open, operm, pointer, quote, Timer, filter, put!, step, uperm, Number, Ptr or count
  No documentation found.

  Binding outer does not exist.

I know it is not a reserved keyword, but still…

7 Likes

That’s a good one. I submitted an issue:

2 Likes

Keywords with exclamation mark

julia> foo!(x; f!) = f!(x)
foo (generic function with 1 method)

julia> bar!(x) = begin x[1] = 0; return x end
bar! (generic function with 1 method)

julia> foo!([1, 2, 3]; f!=bar!)
ERROR: syntax: invalid keyword argument syntax "(f != bar!)" around REPL[18]:1
Stacktrace:
 [1] top-level scope
   @ REPL[18]:1

julia> foo!([1, 2, 3]; f! =bar!)
3-element Vector{Int64}:
 0
 2
 3
4 Likes

Sorry for being a bit square—but this is really not a bad behavior.

  1. this f!=bar! example is pretending that spaces dont matter. they absolutely do, and that is well-known
  2. the runtime is throwing an exception to remind you it is wrong to omit spaces

If we mark this sort of example as a “most harrowing WAT” then what do we call this

julia> f(x) = x
           + 1
1

julia> f(1)
1

Is this now an “unmitigated-disaster-level WAT” because there is no exception?

I hope we can agree that my example here is just a plain old bug because of bad code—and so is the f!=bar! example.

1 Like
julia> x = [1, 2];

julia> d = Dict(x => 1)
Dict{Vector{Int64}, Int64} with 1 entry:
  [1, 2] => 1

julia> push!(x, 3);

julia> d
Dict{Vector{Int64}, Int64} with 1 entry:
  [1, 2, 3] => 1

julia> d[[1, 2]]
ERROR: KeyError: key [1, 2] not found

julia> d[[1, 2, 3]]
ERROR: KeyError: key [1, 2, 3] not found

Moral: Don’t use mutable keys.

10 Likes

Somehow, I am glad this doesn’t work!

The default behavior of == when comparing structs has always been a bit of a WAT to me.

julia> struct A
           x::Vector{Int}
       end

julia> A([1, 2]) == A([1, 2])
false

julia> mutable struct B
           x::Int
       end

julia> B(1) == B(1)
false

In my opinion, the default behavior should be to compare the values of the contents of the structs, regardless of whether the structs or the contents of the structs are mutable.

10 Likes

That’s actually (kind-of) already tracked as a 2.0 issue, though there’s probably more discussion to be had on how exactly this should work.

3 Likes

This is not so much of a WAT for everyone, but… WAT!!!

julia> macro weird(a::Int)
           @show a, typeof(a)
           1
       end
@weird (macro with 1 methods)

julia> macro weird(a::Float64)
           @show a, typeof(a)
           2a
       end
@weird (macro with 2 methods)

julia> macro weird(a::String)
           @show a, typeof(a)
           a^3
       end
@weird (macro with 3 methods)

julia> macro weird(a::Symbol)
           @show a, typeof(a)
           :($(a)^4)
       end
@weird (macro with 4 methods)

julia> macro weird(a::Expr)
           @show a, typeof(a)
           a
       end
@weird (macro with 5 methods)

julia> @weird 10
(a, typeof(a)) = (10, Int64)
1

julia> @weird 1.0
(a, typeof(a)) = (1.0, Float64)
2.0

julia> @weird "Hi"
(a, typeof(a)) = ("Hi", String)
"HiHiHi"

julia> @weird im
(a, typeof(a)) = (:im, Symbol)
1 + 0im

julia> @weird "Hi" ^ 3
(a, typeof(a)) = (:("Hi" ^ 3), Expr)
"HiHiHi"

I would imagine that macro calling logic would consider all arguments as “typeless”, but that doesn’t seem to be the case.

(Without knowing the specifics, I guess that macros are “just” functions underneath that have a special syntax and run on a different moment under the Julia parsing-lowering-compilation-execution pipeline. i.e., just after the parsing and before the lowering).

1 Like