Relaxing type restrictions in method signatures?

Here’s the Hash.zip for example:

  def self.zip(ary1 : Array(K), ary2 : Array(V))
    hash = {} of K => V
    ary1.each_with_index do |key, i|
      hash[key] = ary2[i]
    end
    hash
  end

Given that ary1 only has to provide an #each_with_index and ary2 should only have #[], maybe the signature should be like this?

  def self.zip(keys : Enumerable(K), values : Indexable(V))

That was easy, now for some bonus points:

  • What about an Iterable(K) and Iterable(V)?
  • Should there be an interface for a queue? No random access, just shift-unshift and push-pop?
  • Maybe forget about all the different kinds of vectors and just accept a Slice? Still, an Iterable isn’t convertible to a Slice without calculating all the values.
  • Maybe forget about vectors and just accept Iterables?

What I’m concerned about here are avoidable allocations. Typically I care much less about CPU cycles than heap space.

We can definitely improve Hash.zip. We already did that for Array.zip and others. You should take a look at how it’s done in Array.

Neat!

As well as changing the definition to:

def self.zip(keys : Enumerable(K), values : Indexable(V))

could you also overload with something like:

def self.zip(keys : Iterable(K), values : Iterable(V))
    hash = {} of K => V
    # Convert to Iterator
    keys = keys.each
    values = values.each
 
    key = keys.next 
    while key != Iterator::Stop::INSTANCE
        value = values.next
        raise IndexError.new if value == Iterator::Stop::INSTANCE
        hash[key.as(K)] = value.as(V)
        key = keys.next
    end
    hash
end

No indexing here