Why does this rewrite to a Julia do block break when the lambda works?
Asked Answered
L

1

7

I have this lambda style function call

foldr((l,r) -> r+1, "12345"; init=0)

which Julia happily computes as 5. Rewriting it to the do style looks like

foldr("12345"; init=0) do (l,r)   # I also tried ((l,r),) and just tup
    # l,r = tup   # and then destructure here
    r+1
end

As far as I understood, these two should be equivalent. Unfortunately, Julia 1.7.0-rc3 doesn't agree with me:

ERROR: MethodError: no method matching (::var"#36#37")(::Char, ::Int64)
Closest candidates are:
(::var"#36#37")(::Any) at REPL[25]:2
Stacktrace:
[1] FlipArgs
@ ./reduce.jl:196 [inlined]
[2] BottomRF
@ ./reduce.jl:81 [inlined]
[3] _foldl_impl(op::Base.BottomRF{Base.FlipArgs{var"#36#37"}}, init::Int64, itr::Base.Iterators.Reverse{String})
@ Base ./reduce.jl:58
[4] foldl_impl(op::Base.BottomRF{Base.FlipArgs{var"#36#37"}}, nt::Int64, itr::Base.Iterators.Reverse{String})
@ Base ./reduce.jl:48
[5] mapfoldr_impl(f::Function, op::Function, nt::Int64, itr::String)
@ Base ./reduce.jl:186
[6] #mapfoldr#246
@ ./reduce.jl:205 [inlined]
[7] #foldr#247
@ ./reduce.jl:224 [inlined]
[8] top-level scope
@ REPL[25]:1

Why does the first form work but the second not? Can it be made to work?

Lunula answered 15/12, 2021 at 20:2 Comment(0)
I
4

Parentheses are not needed here:

julia> foldr("12345"; init=0) do l, r
           r+1
       end
5

if you write (l, r) after do it means you are passing one argument that has two elements that should be assigned to l and r respectively, e.g.:

julia> foreach([1:2, 3:4, 5:6]) do (x,y)
       @show x
       @show y
       end
x = 1
y = 2
x = 3
y = 4
x = 5
y = 6

See the Julia Manual for an additional explanation of this rule.

Insipid answered 15/12, 2021 at 20:9 Comment(3)
This doesn't only happen with do blocks, either, you could define a function as f( (x, y) ) = x+y; it only takes 1 argument and tries to iterate it for 2 elements to assign to x and y. If you annotate its argument type for dispatch, you'll have to do this f( (x, y)::UnitRange ) = x-y. Multiple assignment like x, y = someIterable is called destructuring, and method argument destructuring is specifically documented in depth here: docs.julialang.org/en/v1/manual/functions/…Tailspin
Oh my, of course! I feel stupid. Wasn't there an older version of Julia where you needed to do it like this?Lunula
I have just checked the Julia 0.3 documentation and it uses do a, b, so it seems that it was always the recommended syntax (but indeed maybe in older Julia versions if you put parentheses around a, b it worked since tuple destruction in method signatures was not supported then)Methadone

© 2022 - 2025 — McMap. All rights reserved.