ruby picture

RCR 245: zip's broken metaphor

Submitted by neoneye (Wed Apr 14 07:04:56 UTC 2004)

Abstract

Enumerable#zip is good at making pairs. In a way that is intuitive and easy to grasp.

#zip is taking a block. Guess what happens.

(1..4).zip(1..4) {|a,b| a*b }

Problem

The problem is that nothing happens, the zip returns nil. It shows up that zip is commonly being used for multiway each! Thats a broken metaphor, which give people bad habits. See [>ruby-talk:97043] for discussion.

Proposal

I propose that zip concatenates and returns the result of the supplied block.

(4..6).zip(10..12) {|a,b| a*b } #=> [40, 55, 72]
%w(a b c).zip(1..9) {|i| i.reverse } #=> [[1, 'a'], [2, 'b'], [3, 'c']]

Analysis

incompatibility for people which has used zip for multiway each. However they should use callcc/enumerator instead of this bad habbit.

Implementation

don't know what to write.
ruby picture
Comments Current voting

David Black:

I'm not sure about all this 'bad habit' and 'broken' stuff, but I do find it odd that #zip returns nil with a block.

Just for fun, here's an implementation:


          
  module Z
    def Z.included(c)
      c.class_eval {
        alias oldzip zip
        def zip(*others,&block)
          if block
            res = []
            newblock = Proc.new { |*args| res << block.call(*args) }
            oldzip(*others,&newblock)
            return res
          else
            oldzip(*others)
          end
        end
        }
    end
  end

class Array; include Z; end class Range; include Z; end
  # and perhaps track down every Enumerable and do likewise....

(BTW, your 'reverse' example doesn't need the square brackets around i.reverse. Each element will already be an array.)


Fixed the reverse example. Thanks David.

I made another implementation (didn't use each). Buts thats not of interest [>rubytalk:97006]. Lacks time to do a nice implementation with enumerator/callcc.. maybe when I get really tired I do that ;-)

BTW: I cannot seem to get links working inside this RCR comment.

-- Simon Strandgaard


Just looked at the source code and I think your proposal would lead to unnecessary array creation in case you don't need one.

If you need an Array, you can type 4 letters extra: a.zip(b).map {...} => [...]

It's the same like #each returning self.

Although I'm not a native speaker, I don't think, zip is semantically demanding an array as return value.

Could you explain, how enumerator can be used, I have no idea ?

-- Matthias Georgi


enumerator can be used to convert from one type of each to another. continuations can be used to restart the program. Together they can be used for multiway each.

I have made an iterator class, which is useful for multiway each. See >homepage.

require 'iterator'
data_a = %w(a b c d)
data_b = (0..3)
ia = Iterator::Continuation.new(data_a, :each)
ib = Iterator::Continuation.new(data_b, :each)
result = []
while ia.has_next? and ib.has_next?
    result << ia.current
    result << ib.current
    ia.next
    ib.next
end
ia.close
ib.close
p result   # ["a", 0, "b", 1, "c", 2, "d", 3]


OK, looks like Generator, SyncEnumerator from stdlib. But I would only use them to pass Iterators around or to avoid recursion in combination with a stack. For general use it's too javalike, i was so confident not to type these "while next?" thing anymore. So probably it's personal taste, for me it's a good habit to write


          
  [ 1, 2, 3 ].zip([ 10, 20, 30 ]).map {|i,j| i * j } 

          

I use this very often and Iteration based on Continuation is also not very performant.

-- Matthias Georgi


Yes its java like.. it was just an example of how multiway each, over a one zillion of elements could be accomplished. People with such needs, should probably use C rather than using zip for this. Such demands is very specialized. I think zip rather should be targeted for the most common usage.

Have you ever felt wanting to supply a block to zip itself?

BTW: How do you convince a Perl girl to use Ruby, if you have to explain to her, that zip, is not zip in that paticular case, and you have to do a map afterwards!

-- Simon Strandgaard


OK, i see the inconsistency. I'm not sure, what i expect from zip. open() for example returns also different values, with and without a block.

-- Matthias Georgi


Great ;-)

Usually open is some kind of factory method, and factories can build many kinds of products. zip is not really in that category.

For instance map returns the same as if you had supplied a simple block. The same would be nice with zip.

server> irb
irb(main):001:0>  a = %w(a b c)
=> ["a", "b", "c"]
irb(main):002:0> a.map
=> ["a", "b", "c"]
irb(main):003:0> a.map{|i| i}
=> ["a", "b", "c"]
irb(main):004:0> a.zip
=> [ ["a"], ["b"], ["c"] ]
irb(main):005:0> a.zip{|i| i}
=> nil        ## [ ["a"], ["b"], ["c"] ] is what this RCR is about.
irb(main):006:0>

The last line is what this RCR is about.

-- Simon Strandgaard


Strongly opposed 1
Opposed 1
Neutral 4
In favor 1
Strongly advocate 1
ruby picture
If you have registered at RCRchive, you may now sign in below. If you have not registered, you may sign up for a username and password. Registering enables you to submit new RCRs, and vote and leave comments on existing RCRs.
Your username:
Your password:

ruby picture

Powered by .