Writing Good #each Methods?


#1

I’m working my way through The Ruby Course slides at the moment and am
on the section about dynamicity. Specifically I’m on slide 59 right
now, the slide about a ListItem – essentially a link list.

So, this got me thinking: what makes a good #each method? I’d love to
hear any pearls of wisdom if any of you have the time.

Thank you,

James H.


#2

So, this got me thinking: what makes a good #each method? I’d love to
hear any pearls of wisdom if any of you have the time.

I think an each method should have the following properties

  • All elements must be yielded
  • Each element must be yielded exactly once
  • If the container has some order associated with it (ie Array or
    SortedSet) the elements should be yielded in order
  • If there is more than ordering (such as a post, pre or in order
    walk round a binary tree) then the elements should be yielded
    according to the ordering that is as closest to ‘natural’ ordering
    (inorder in the case of a binary tree). each methods for the other
    orderings (each_preorder, each_postorder) must also be provided.

That’s my opinion, I’m sure other people might disagree with me here.

Farrel


#3

I’d also add

  • If appropriate, inspect the arity of the block and provide the
    requested number of parameters.

Hash#each’s behaviour is a good example:
hash = {:a=>1, :b=>2}
hash.each { |x| p x }

[:b, 2]

[:a, 1]

hash.each { |k,v| puts “#{k.inspect} => #{v.inspect}” }

:b => 2

:a => 1

Paul.


#4

“Paul B.” removed_email_address@domain.invalid writes:

:b => 2

:a => 1

You don’t need to – just yield the array.

irb(main):001:0> class C
irb(main):002:1> def each
irb(main):003:2> yield [1,2]
irb(main):004:2> yield [3,4]
irb(main):005:2> end
irb(main):006:1> end
=> nil
irb(main):007:0> C.new.each{|a| p a}
[1, 2]
[3, 4]
=> nil
irb(main):008:0> C.new.each{|a,b| p [a,b]}
[1, 2]
[3, 4]
=> nil


#5

You don’t need to – just yield the array.

Oops. You are, of course, correct. If it is just an array, that
behaviour comes automatically.

Wouldn’t it be nice if inject did the same, though? E.g.
hash.inject([]){ |acc, key, value| … }

Paul.


#6

Paul B. schrieb:

Wouldn’t it be nice if inject did the same, though? E.g.
hash.inject([]){ |acc, key, value| … }

Paul, you have to use parentheses:

hash.inject([]){ |acc, (key, value)| … }

Regards,
Pit


#7

“Paul B.” removed_email_address@domain.invalid writes:

Wouldn’t it be nice if inject did the same, though? E.g.
hash.inject([]){ |acc, key, value| … }

You can come close:

hash.inject([]){ |acc, (key, value)| … }


#8

hash.inject([]){ |acc, (key, value)| … }

Thanks! I hadn’t thought to try that before. It’s definitely going to
make my future use of inject more concise and readable.

Paul.


#9

I’m a little confused by this. As I understand it, “arity” is the
number of arguments to a term. Perhaps I’m just not seeing it in your
example, but where does arity fit into it? Doubly, why might one want
to know this?


#10

Paul B. wrote:

I’d also add

  • If appropriate, inspect the arity of the block and provide the
    requested number of parameters.

And another one

  • return self

    robert


#11

On Mar 14, 2006, at 4:05 AM, Paul B. wrote:

:b => 2

:a => 1

Paul.

This isn’t really a function of Hash#each being smart…

irb(main):001:0> def example()
irb(main):002:1> yield [:a, 1]
irb(main):003:1> end
=> nil
irb(main):004:0> example { |x| p x }
[:a, 1]
=> nil
irb(main):005:0> example { |k, v| p k; p v; }
:a
1
=> nil

As you can see yielding an array automatically gets you this kind of
behavior. There are downsides of course…


#12

Concerning ordered elements, what is the best way to ascertain the
order wherein the structure is not an array? For instance, suppose you
had a link list. You could have a class variable that kept track of
the beginning, but this would mean difficulties when you had more than
one link list in play.

In trying to solve the order issue (naively), I wrote a method like:

def each(&block)
current = self.previous
until current.previous.nil?
current = current.previous
end

end

This obviously doesn’t work because on each call of #each, the focus is
rewound to the intial element in the list.

Any advice on the matter?


#13

I’ve had a few people write me asking about the Ruby Course slides.
Not that I mind replying to each of you, but I thought I’d save the
curious a little time =)

All sorts of Ruby articles and documents can be found at the Ruby Doc
website: http://www.ruby-doc.org

The slides in particular are found here (NB this is a direct link to a
PDF and may at some point change):
http://www.ruby-doc.org/docs/Immersive%20Ruby%20programming%20course/RubyCourse_1.0-1.pdf

Enjoy!

James H.