A most harrowing collection of Julia WATs

Same for me, but my brain wasn’t so fond about the same thing happening to Float32s:

julia> f = 10
10

julia> 2f + 1
21

julia> 2*f+1
21

julia> 2f+1
20.0f0

julia> 2f+1f # (2f+1)*f
200.0f0

https://github.com/JuliaLang/julia/issues/43469

2 Likes

This was definitely wat for me, and is arguably a bug:

julia> 0.0 in Set([-0.0])
false

julia> 0.0 in collect(Set([-0.0]))
true

And on the flip side:

julia> NaN in Set([NaN])
true

julia> NaN in [NaN]
false

Edit: I didn’t read the docs for in closely enough: It’s not a bug, but it definitely is wat

11 Likes

0.0 in Set([-0.0]) is definitely a bug in the implementation (or the documentation) as Collections and Data Structures · The Julia Language says “For example, Sets check whether the item isequal to one of the elements.” (Edit: this is incorrect)

By the way, the specification of Base.in is clearly qualified as a WAT: Base.in may follow == or isequal semantics depending on the argument types.

7 Likes

This is documented the way it works, as the documentation states:

Some collections follow a slightly different definition. For example, Set s check whether the item isequal to one of the elements.

and

julia> 0.0 == -0.0
true

julia> isequal(0.0, -0.0)
false

but

By the way, the specification of Base.in is clearly qualified as a WAT

Fully agreed

5 Likes

To go with your other example of lowering iteration specifications… Here’s the weirdest way I know to define a closure:

julia> for wat() in 1:3
           println(wat())
       end
1
2
3
25 Likes

Thanks, I did check it, and it is definitely in the REPL history. I have no idea why I interpreted it backward.

2 Likes

Who knew we had infix macros?

julia> macro +(a,b)
           QuoteNode((a,b))
       end
@+ (macro with 1 method)

julia> @(x + y*2)
(:x, :(y * 2))

(Please don’t use this! I hesitate to even mention it and I think it should get removed soon.)

39 Likes

@miguelraz the categorization of the countlines() example in your Julia WATs seems wrong to me. I’d class it as “paths are hard”, perhaps. See also

https://github.com/JuliaLang/Juleps/pull/26

Using isequal is the only reasonable behavior, using == is quite broken.

3 Likes

Wow, this is wild.

5 Likes

https://github.com/JuliaLang/julia/issues/44532

4 Likes

Lowering is hard, credit to Jonnie Diegelman :

julia> nums = zeros(Int, 10);

julia> for nums[rand(1:10)] in 1:20
       end

julia> nums
10-element Vector{Int64}:
 12
 16
  7
 20
 19
 18
 15
  0
 13
 17

(Python suffers from something similar). Explanation: As Jabon Nissen pointed out, “It’s because for i in 1:20 lowers to for i = 1:20 in Julia. Here, it’s nums[rand(1:10)] = 1:20”

I do find this behavior very surprising, but I don’t think it has anything to do with lowering. It’s equally surprising to me with = as with in (after all, nums[rand(1:10)] = 1:20 is not a valid assignment anyway).

Maybe an example like this helps explain what is going on:

julia> x = [0,0]
2-element Vector{Int64}:
 0
 0

julia> for x[1] in 1:2; println(x); end
[1, 0]
[2, 0]

or the example by Chris_Foster above?

I wonder if this was deliberately implemented or is just a bug, since it doesn’t seem to be mentioned in the docs (Control Flow · The Julia Language). In fact, the docs seem to imply that the for line always defines new variables. Also, it doesn’t work with let:

julia> let x[1] = 1; end
ERROR: syntax: invalid let syntax around REPL[28]:1
1 Like

In fact, the docs seem to imply that the for line always defines new variables.

The linked documentation seems very clear to me:

Here the 1:5 is a range object, representing the sequence of numbers 1, 2, 3, 4, 5. The for loop iterates through these values, assigning each one in turn to the variable i. One rather important distinction between the previous while loop form and the for loop form is the scope during which the variable is visible. If the variable i has not been introduced in another scope, in the for loop form, it is visible only inside of the for loop, and not outside/afterwards.

Maybe the term variable is inaccurate, as the for loop clearly lowers the code in a way that setindex is called, but I either expected this to be an error or to work the way it works (i.e. working like x[1] = iteration_value was called at the start of the loop each iteration).

A lot of this is just people omitting spaces where they really should not omit spaces. Use a linter or a coding style or something…

It’s equivalent to writing

f(x) = x
    + 1

and then wondering why f(1) == 1.

A good source of wats is the where keyword:

julia> isa isa where where where
true
50 Likes

It’s very clear only about the little it says, which is only about variables. And to me, the fact that it consistently says “variable” and not “expression” suggests that it only works for variables. And the part about scope seems to say that the variables to the left of the = or in are new variables (although technically the paragraph only covers the case where there is no existing variable of the same name in the enclosing). So I would not expect to be able to refer to existing variables at all in this context. The docs about scope also seems consistent with that.

The fact that for x = 1:3 introduces a new x, while for x[1] = 1:3 refers to x from an outer scope seems pretty odd to me. (The closure example for g() = 1:3 makes a lot more sense, but is pretty useless.)

1 Like

A lot of this is ambiguities arising from allowing 2f to mean 2*f. The spaces required for disambiguation are unnecessary in many other languages.

2 Likes

From my perspective that explains how folks develop this bad habit—it does not justify it.

1 Like

Different people, different interpretations.

The for loop iterates through these values, assigning each one in turn to the variable i.

The first phrase talks about assigning to i even before the text says that for specifically can implicitly introduce new variables, consequently it can only be talking about a variable i that already exists.

One rather important distinction between the previous while loop form and the for loop form is the scope during which the variable is visible. If the variable i has not been introduced in another scope, in the for loop form, it is visible only inside of the for loop, and not outside/afterwards.

The rest of the paragraph talks about the exception in behavior of the for loop, in the specific case the variable was not introduced before, consequently it leaves implicit the previously stated behavior is expected outside this specific case; otherwise it should mention that, differently from the while loop, the variable used to control the loop cannot be declared before it.

But yes, instead of variable, the text should probably say assignment location (or assignable location), which is an expression used in some Julia error messages.

julia> a = [1]
1-element Array{Int64,1}:
 1

julia> true && a[1] = 2
ERROR: syntax: invalid assignment location "true && a[1]" around REPL[2]:1
Stacktrace:
 [1] top-level scope at REPL[2]:1

I can see how this is a possible interpretation, but under that interpretation the docs are just wrong, rather than simply misleading. for x in .. introduces a new x regardless of whether x is already defined:

julia> x = 2
2

julia> for x = 1:5
       end

julia> x
2