Comment on this RCR (edit wiki page) | RCRchive home

RCR 245: zip's broken metaphor

submitted by neoneye on Wed Apr 14 2004 03:19:56 AM -0700

Status: pending


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 [] 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.

Vote for this RCR

Strongly opposed [1]
Opposed [1]
Neutral [2]
In favor [1]
Strongly advocate [1]

Change the status of this RCR to:

accepted

rejected

withdrawn


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 []. 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 .

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

Add comments here


Back to RCRchive.


RCR Submission page and RCRchive powered by Ruby, Apache, RuWiki (modified), and RubLog