There are also seem to be other inconsistencies with methods Number#+ & Number#- as well. I think these may be due to the fact that these 2 methods can take either 0 or 1 argument (be either a unary or binary operator).
2.+
returns 2 which is expected, as 2.+ is the same as +2. But
2 +
gives the error âno overload matches âInt32#+â with type Nilâ, whereas I would have expected âwrong number of argumentsâ.
And:
[1,2].reduce(&.*)
doesnât compile with the error âwrong number of argumentsâ. I would have expected this to compile & return 2, like Ruby does.
It appears that Crystal is attempting to call the method with no arguments. To test:
struct Int32
def test
puts "No arg"
3
end
def test(i)
puts "Arg: #{i}"
self + i
end
end
puts 2.test
puts 1.test 4
puts [1,2,3].reduce(&.test)
Gives the output:
No arg
3
Arg: 4
5
No arg
No arg
3
Yep, the method in the reduce block is being called with no arguments. Bug?
My bad, sorry.
The next line in the file was a puts, so the compiler was probably using that as an argument to â+â. Please ignore that part of my post.
Thatâs expected behaviour. The &.test syntax just exands to calling test on the first argument of the block. So reduce(&.test) is equivalent to reduce { |acc| acc.test }. The second argument is never used by this block.
There is currently no way to use the short block argument syntax with more than one argument. There has been discussions about adding a short syntax for referencing positional block arguments, as introduced in Ruby 2.7 (RFC: `&.` within a block - #12 by vlazar).
That feature would allow using short argument syntax in the way you describe:
Fair enough. I expected array.reduce(&.+) in Crystal to work the same as array.reduce(&:+) in Ruby. I guess thatâs a function of Crystal being so similar to Ruby.
It would be a shame to have to resort to that ugliness for a simple case like this where the number of block arguments matches the number of arguments of the method (taking the first one as the object, that is).
Yeah, they look very much alike and have a similar purpose. But the behaviour is very different. The Crystal variant is actually more powerful which means it needs more explicitness for some use cases.
In Ruby &:foo refers to a method name. It is interpreted as a call to Symbol#to_proc. This method returns a proc which calls method foo on its first argument and passes the following arguments to that method.
In Crystal, &.foo is actually a method call - in this case without arguments. But you can also pass arguments directly to the method, for example &.foo("bar"). This is a very useful feature and not possible with Rubyâs approach. In turn, it makes it harder to pass block arguments to the method. It couldnât work like in Ruby anyway because Crystal doesnât have dynamic dispatch.
Thanks @asterite, I am aware of Array#sum, but mind that Iâm raising a different set of concerns.
The general question, beyond #reduce, is does it make sense to allow shorthand syntax when the expected proc taks two arguments or more?
The other question is does it make sense to define #+ and #- on Number? Are there other languages taking a similar approach?
I suppose so. Block arguments donât need to be consumed. Itâs perfectly valid to ignore the following arguments, which is often used for example by iterators which pass in the index as second argument. Thatâs perfectly fine. Only in the case of short argument syntax it might be mildly confusing, but really only when youâre spoiled by Ruby. But when youâre aware that reduce(&.foo) is just the parameter-less version of reduce(&.foo()) it should be clear that no block arguments will be passed forward.
These methods implement the unary operators which we certainly want to exist.
But when youâre aware that reduce(&.foo) is just the parameter-less version of reduce(&.foo()) it should be clear that no block arguments will be passed forward.
Fine by me, but what is a scenario where calling reduce with a proc consuming only one arg makes sense? I cannot think of any, so it looks to me like weâre making it extremely easy to introduce bugs in oneâs code. Anyhow, I donât mean to drag this any further, if Crystal users are not bothered by this. Iâm just hoping you see where Iâm coming from.
These methods implement the unary operators which we certainly want to exist.
Ah, I got confused. For a moment I thought a = 3 + was a valid statement. It isnât
To summarise, +3 is just syntactic sugar for 3.+, which makes perfect sense, sorry for the misunderstanding.
The shorthand syntax is just syntax sugar: itâs expanded by the compiler to the longer form. When itâs time to analyze the program, the compiler has no idea whether you wrote reduce(&.+) or reduce { |arg| arg.+ } because they are exactly the same. In fact, thatâs why we say itâs a shorthand syntax. If not, it would be something different.
The other question is does it make sense to define #+ and #- on Number? Are there other languages taking a similar approach?
Ruby does that, although they are called @+ and @-, but just because Ruby doesnât have method overloading.
I also mentioned sum because the only cases of reduce(&.operator) that come to mind are + and * and those are already covered with sum and product. So the shorthand syntax is pretty much useless with reduce.
For reduce, thereâs obviously meaningful use case for a block that uses only the first arg. So the bottom line is, you effectively canât use the short syntax for that. (unless we introduce a means to reference positional block arguments as suggested in RFC: `&.` within a block - #12 by vlazar)
Surely the issue is the semantics of â&.methodâ when it has no arguments, rather than âdynamic dispatchâ. The compiler knows how many arguments are being passed to the block, so if the semantics were changed such that no arguments to â&.methodâ means âuse them allâ, then the above would work like Ruby. Whether this is desirable or not is another question. Then if you really want to force no arguments to the method you could always say â&.method()â, right?
lbarasti:
does it make sense to define #+ and #- on Number?
Technically, this could work. But it would be a bad idea for semantics to depend on the absence of parenthesis in a method call. That would be different from anywhere else in Crystal.
Actually I have used reduce(&:method) in Ruby a few times, calling my own methods. Reduce is really neat for some string processing, sort of like join but where you need to do extra stuff along the way. Reduce is nice & concise - in particular it is useful to avoid the need for special handling of either the first or last element. I just canât use the shortcut syntax in Crystal.
I guess it depends on how a coder looks at the syntax â&.methodâ. If you think of the â&â as a positional parameter of the block, then I agree with you because the syntax then looks like a method call, and I guess in future it could be rewritten as â(_1).methodâ. However if you consider â&.â to be an operator which conceptually wraps method somehow so that it is called in place of yield then it makes sense to pass all the arguments. I guess Ruby doesnât have this issue using â:â instead of â.â, and maybe a different syntax like vlazar suggests is a good idea.