Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Hash (and others) yield tuples in each #2781

Closed
wants to merge 1 commit into from

Conversation

asterite
Copy link
Member

@asterite asterite commented Jun 8, 2016

This is an alternative to #2778, proposed in this comment.

Basically, now you have to do:

hash = {1 => 2}
hash.each do |(key, value|)|
  key # => 1
  value # => 2
end

If you do do |key, value|, key will be a tuple and value will be nil. We can of course discuss if this should actually be a compile error instead of binding the variable to nil.

I also changed the order in Enumerable#reduce to be elem, memo, which is more consistent with everything else.

HTTP::Headers and ENV are also changed to this new behaviour, and now include Enumerable.

Now, my opinion about this vs. #2778 . If you look at the diff you'll see most changes change |key, value| with |(key, value)|. To me, this adds no information at all, and feels redundant and is definitely more tedious to type. It's hard to get confused if auto-unpacking would be turned on, because key and value are self-describing names, so someone reading the code would know what to expect.

The same thought applies to changes from |key, value, index| to |(key, value), index|, it feels like the language is forcing me to add parentheses for no real reason (no more type safe, and I could still use the explicit unpacking if I wanted to).

Additionally, in some places there's each_with_index do |elem, index| and one might wonder why it's not |(elem, index)| instead. Maybe it's because the iterated element, and the index, are separate entities. Should we change Array#zip to return tuples, or yield two elements? If a class isn't Enumerable but yields two things in a method, should one use a tuple or not? All these questions start to appear. Auto-unpacking make them all go away, and leaves the door open for the programmer, on the call site, when writing the block, to choose explicit unpacking, or implicit auto-unpacking.

This PR of course breaks most code out there, as they need to upgrade to the new each method. Auto-unpacking, on the other hand, is almost backwards compatible, unless you did something like hash.each { |key| ... } (didn't happen in the bunch of projects I tested).

If Crystal is to be a pragmatic, less noisy, practical language, I think #2778 is the way to go. But of course this approach isn't that bad either, it just feels a bit more cumbersome to code like this, though it's always explicit and there's no "magic".

@jhass
Copy link
Member

jhass commented Jun 8, 2016

I also changed the order in Enumerable#reduce to be elem, memo, which is more consistent with everything else.

That might make sense for the version that takes an argument, but it falls apart for the argument less version. The perspective to take here is not that the argument gives the default value for memo, but that it is prepended to the collection. In any case then, the initial first arguments for the block passed to reduce are taken from the first two elements of the collection respectively. Swapping the arguments, the symmetry between the argumentless version of reduce and its counterpart is lost.

@asterite
Copy link
Member Author

asterite commented Jun 8, 2016

But reduce, in both forms, always yields an element and the memo. The memo is the first element in the collection in the argless case. Why order matters?

@kostya
Copy link
Contributor

kostya commented Jun 8, 2016

why you not consider {}, instead of (), i think it more correct:

hash = {1 => 2}
hash.each do |{key, value}|
  key # => 1
  value # => 2
end

@asterite
Copy link
Member Author

asterite commented Jun 8, 2016

@kostya you can do:

ary = [[1, 2], [3, 4]]
ary.each do |(x, y)|
  x # => 1
  y # => 2
end

Basically, you can unpack anything that responds to [].

Because {...} means tuple someone might expect that it only works with tuples, but that's not the case. That's why () is used. But I think I wouldn't mind using {...} either.

@jhass I can probably remove the reduce change, it doesn't belong in this PR.

@jhass
Copy link
Member

jhass commented Jun 8, 2016

I think the current version visualizes like this

           a, b, c, d
           |  |
reduce do |x, y|

        --->  n, a, b, c, d
       |      |  |
reduce(n) do |x, y|

While yours visualizes more like

           a, b, c, d
            \/
            /\
reduce do |x, y|

              a, b, c, d
              | 
reduce(n) do |x, y|
       |_________|

That is people will a lot less likely think (and explain it) as "the argument is simply prepended to the collection", but rather "With an argument, it becomes the first value for memo, without an argument the first value becomes the first value for memo and the other argument is actually the second value". I think that explanation is more complicated especially in hindsight to what reduce does, passing its return value back as memo.

Another way to argue this is visualizing the reduction:

# currently
a b c d e f
ab c d e f
abc d e f
abcd e f
abcdef

# Your version
a b c d e f
ba c d e f
cba d e f
dcba e f
edcba f
fedcba

I constantly have to flip the arguments in my mind compared to them just reducing down left to right.

@jhass
Copy link
Member

jhass commented Jun 8, 2016

Perhaps we should rename memo, it's probably not a good name for what the argument actually is most of the time, the result of the previous iteration, not a memorized value.

@asterite asterite force-pushed the feature/hash_tuple branch from 252aa00 to 7064fe8 Compare June 9, 2016 01:11
@asterite
Copy link
Member Author

Closed in favor of #2783

@asterite asterite closed this Jun 10, 2016
@asterite asterite deleted the feature/hash_tuple branch June 10, 2016 13:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants