Help me understand pipes - strange behavior

I’m running into something really strange for me in a pipeline. Can someone please explain why these two produce different results?

julia> "0X" |> x -> collect(UInt8, x) .|> x -> x == UInt8('X') ? UInt8(':') : x .|> x -> x - UInt8('0')
2-element Vector{UInt8}:
 0x00
 0x3a

julia> ("0X" |> x -> collect(UInt8, x) .|> x -> x == UInt8('X') ? UInt8(':') : x ) .|> x -> x - UInt8('0')
2-element Vector{UInt8}:
 0x00
 0x0a

All I did differently was add parenthesis in the second one and then it magically “works”. But to me the first one should “work” too, but it doesn’t. What’s happening and what should I keep in mind so I don’t get bit by this again?

You’re just confused by precedence of operators, try to run one pipe at a time and see when parentheses made a difference, and then you will figure out which operator has the precedence you didn’t expect

julia> 1 |> x -> x==1 ? 'a' : 'b' |> x -> 'c'
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)

julia> 1 |> (x -> x==1 ? 'a' : 'b') |> x -> 'c'
'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)

julia> 1 |> x -> x==1 ? 'a' : ('b' |> x -> 'c')
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)

basically what you intended is 2nd line, but 1st line is actually equivalent to 3rd line

Thank you for your help, @jling. That is really confusing. I would expect pipes to work the same way as in bash. If you do some stuff | other stuff you almost always get (some stuff) | (other stuff). some (stuff | other) stuff doesn’t make any sense. Why would julia not make the pipes parse last?

Does one have to defensively wrap everything in () when using |> or .|> or just ternary operations?

Bash doesn’t have anonymous functions, which is where things are breaking down.

Because you can pipe inside functions too, and if that’s not what you intend, you should use some syntax to delineate where an anonymous function starts and ends. Bash has {} for that, here you can use parentheses. Anonymous functions also have the function block syntax with end, though 1-liners need a newline semicolon if you’re returning an expression inside parentheses or brackets e.g. function (x); (x,) end.

julia> "0X" |> (x -> collect(UInt8, x)) .|> (x -> x == UInt8('X') ? UInt8(':') : x) .|> (x -> x - UInt8('0'))
2-element Vector{UInt8}:
 0x00
 0x0a

julia> "0X" |> function (x) collect(UInt8, x) end .|> function (x) x == UInt8('X') ? UInt8(':') : x end .|> function (x) x - UInt8('0') end
2-element Vector{UInt8}:
 0x00
 0x0a

Thanks @Benny . :pray: