Slurping in not final position

I was running the roames notebook exercises, in an attempt to fix this:

Create a more generic reduce2 function that works recursively over a tuple

For the operations + and *, being associative, simulate the operation in this direction 1*(2*(3*(4))) or on the contrary (((1)*2)*3)*4, the result is the same.
However, wanting to extend the result also to the operations (-) and (/) I tried to create the second structure, i.e. ((((1/2)/3)/4)/5) which is the one adopted by the library reduce function.
In the absence of slurping in a not final position, I had to use this somewhat convoluted method.

reduce3(f, t::Tuple)= _r(f,reverse(t)...) 
_r(f,(x,))=x
_r(f,h,t...)=f(reduce3(f,reverse(t)),h) 

I’ve tried using non-trailing slurping, which I thought was possible in version 1.9 of julia, also as function arguments as well as for assigning values to multiple variables.

Why was this possibility not extended to function arguments?

With “non-trailing slurping”, are you referring to something like this:

f(a, b..., c, d) = # something

? If not, which syntax exactly are you referring to?

yes I tried to use this expression

_r(f,t...,h) =...

I take the opportunity to ask another question.
Why do the following two definitions seem equivalent?

julia> g((x,))=x^2
g (generic function with 1 method)

julia> g(2)
4

julia> g(x)=x^2
g (generic function with 1 method)

julia> g(2)
4

If you allow slurping in non-trailing position, the following two method definitions would always be ambiguous:

f(a, b, c..., d) = # something
f(a, b..., c, d) = # something

There are lots of possible combinations to get such an ambiguity, so I think that’s why it’s likely not supported.

Because g((x,)) = ... is the same as g(x,) = ..., which is the same as g(x) = ....

2 Likes

Thank you!
On the main question (that of slurping) I have to think about it calmly.
For the secondary question: why don’t the same parsing rules hold for the case of a tuple with two elements?

julia> g((x,y))=x+y
g (generic function with 1 method)

julia> g(x,y)=x+y
g (generic function with 2 methods)

No, it’s not the same. The first one does tuple destructuring, the last one doesn’t.

julia> f1((x,)) = x^2
f1 (generic function with 1 method)

julia> f2(x) = x^2
f2 (generic function with 1 method)

julia> f1((2, 3))
4

julia> f2((2, 3))
ERROR: MethodError: no method matching ^(::Tuple{Int64, Int64}, ::Int64)
2 Likes

So the two expressions (two methods!??!, why only one method then?)) are equivalent only for input of a single element?

julia> g((x,))=x.^2
g (generic function with 1 method)

julia> 

julia> g(((2,3),))
(4, 9)

julia> g(3)
9

julia> g(2,3)
ERROR: MethodError: no method matching g(::Int64, ::Int64)

julia> g(x)=x.^2
g (generic function with 1 method)

Both g(x) = ... and g((x,)) = ... define g(::Any), so the one defined last overwrites the first one.

That’s a side effect of numbers being iterable, so you can destructure a single scalar.

julia> f((x,)) = x
f (generic function with 1 method)

julia> f(1)
1

julia> f("foo")
'f': ASCII/Unicode U+0066 (category Ll: Letter, lowercase)

julia> struct Foo end

julia> f(Foo())
ERROR: MethodError: no method matching iterate(::Foo)
2 Likes

I don’t want to abuse your availability, but I also try to ask the reason for the following results:

julia> t,h...=1
1

julia> t
1

julia> h
Base.Iterators.Rest{Int64, Nothing}(1, nothing)

julia> t...,h=1
1

julia> t
Int64[]

julia> h
1

julia> k,t...,h = 1
ERROR: ArgumentError: The iterator only contains 0 elements, but at least 1 were requested.

This assigns the first element to t, and the rest to h. In this case, h is empty, as there is only one element on the right-hand side.

In this case, the last element is assigned to h, and the elements before that are assigned to t. Since there is only one element on the right, t is empty.

In this case, the first element is assigned to k, the last to h, and the ones in between to t. However, as there is only one element on the right-hand side, the operation leads to an error. This assignment expects at least two values on the right.

julia> k, t..., h = 1,2
(1, 2)

julia> t
()

julia> k
1

julia> h
2
2 Likes

Okay. I understand. Although I’m still curious as to why t... is Int[] in one case, Base.Iterators.Rest{Int64, Nothing}(1, nothing) in another, and yet another ().

In the meantime I have found, by trial and error, a way to simulate slurping even in non-final position

julia> reduce4(f, t::Tuple)= _r(f,t...) 
reduce4 (generic function with 1 method)

julia> _r(f,x)=x
_r (generic function with 1 method)

julia> _r(f,(t...,h)...)=f(reduce4(f,t),h)
_r (generic function with 2 methods)


julia> reduce4(*, (1,2,3,4,5))
120

julia> reduce4(+, (1,2,3,4,5))
15

julia> reduce4(-, (1,2,3,4,5))
-13

julia> reduce4(/, (1,2,3,4,5))
0.008333333333333333

!