Class and Iterator Design Question

J

Jim Freeze

This may be a silly design question, but I always balk at
the right answer when I am confronted with it.

I have a class that manages a list and users need to iterate over that list=
 
D

Damphyr

Jim said:
This may be a silly design question, but I always balk at the right
answer when I am confronted with it.

I have a class that manages a list and users need to iterate over
that list. The way I see it, I have to basic alternatives:

# Give user access to the array and let them iterate over Array class
Pea attr_reader :pods end Pea.new.pods.each { |pod| ..do stuff.. }

or

# Provide a custom iterator class Pea def each_pod @pods.each { |pod|
yield pod } end end Pea.new.each_pod { |pod| ..do stuff.. } end

In other words, for classes that manage a list of items, do people
prefer to see a custom iterator, such as #each_<item>, or do they
prefer getting back an array and iterating over it themselves, such
as #<items>.each?

Pea.new.each_pod { |pod| ..do stuff.. } Pea.new.pods.each { |pod|
..do stuff.. }
Encapsulate, encapsulate, encapsulate!
I prefer not exposing the innards.
Define each, mixin Enumerator and let Ruby do it's magic.
Cheers,
V.-

____________________________________________________________________
http://www.freemail.gr - äùñåÜí õðçñåóßá çëåêôñïíéêïý ôá÷õäñïìåßïõ.
http://www.freemail.gr - free email service for the Greek-speaking.
 
H

Hugh Sasse

This may be a silly design question, but I always balk at
the right answer when I am confronted with it.

I have a class that manages a list and users need to iterate over that list.
The way I see it, I have to basic alternatives:

# Give user access to the array and let them iterate over Array
class Pea
attr_reader :pods
end
Pea.new.pods.each { |pod| ..do stuff.. }
Which exposes implementation somewhat, but....
or

# Provide a custom iterator
class Pea
def each_pod
@pods.each { |pod| yield pod }
end

# Maybe:-
alias :each :each_pod
end
Pea.new.each_pod { |pod| ..do stuff.. }
end

In other words, for classes that manage a list of items,
do people prefer to see a custom iterator, such as #each_<item>,
or do they prefer getting back an array and iterating
over it themselves, such as #<items>.each?

Pea.new.each_pod { |pod| ..do stuff.. }
Pea.new.pods.each { |pod| ..do stuff.. }

I'd expect
Pea.new.each{ |pod| ... ]

because you might change your list to a Set or tree or soemthing
later. Unless you normally iterate over something else...
(But having said that, I'd expect a Pod to contain Peas :)!)
Hugh
 
E

Eric Mahurin

Don't you want peas in a pod and not the other way around :)

I think either is fine with a couple changes:

alternative #1: in the docs say that #pods returns an
Enumerable not an Array. This gives you the freedom to change
the implementation to use a Set or some other Enumerable to
hold the pods.

alternative #2: as long as you don't have multiple each
methods, call the method #each instead of #each_pod and include
Enumerable.

--- Jim Freeze said:
This may be a silly design question, but I always balk at
the right answer when I am confronted with it.
=20
I have a class that manages a list and users need to iterate
over that list.
The way I see it, I have to basic alternatives:
=20
# Give user access to the array and let them iterate over
Array
class Pea
attr_reader :pods
end
Pea.new.pods.each { |pod| ..do stuff.. }
=20
or
=20
# Provide a custom iterator
class Pea
def each_pod
@pods.each { |pod| yield pod }
end
end
Pea.new.each_pod { |pod| ..do stuff.. }
end
=20
In other words, for classes that manage a list of items,
do people prefer to see a custom iterator, such as
#each_<item>,
or do they prefer getting back an array and iterating
over it themselves, such as #<items>.each?
=20
Pea.new.each_pod { |pod| ..do stuff.. }
Pea.new.pods.each { |pod| ..do stuff.. }
=20
Cheers



=09
__________________________________=20
Yahoo! Mail - PC Magazine Editors' Choice 2005=20
http://mail.yahoo.com
 
B

Bob Hutchison

This may be a silly design question, but I always balk at
the right answer when I am confronted with it.

I don't think this is a silly question at all. I'm having to deal
with it myself right now. I never seem to come up with the same
answer twice in a row.

[snip]

In other words, for classes that manage a list of items,
do people prefer to see a custom iterator, such as #each_<item>,
or do they prefer getting back an array and iterating
over it themselves, such as #<items>.each?

Pea.new.each_pod { |pod| ..do stuff.. }
Pea.new.pods.each { |pod| ..do stuff.. }


There is another possibility, have the pods accessor return a
(shallow) copy of the original.

I've done all three.

These days I just return the array and let the user do what they
want. This saves me a lot of work at the risk of potentially ruining
the integrity of the object structure. Seemed like a good idea at the
time, but putting it that way... :) Actually I don't think it is
that bad. If I was worried, I'd return a copy. I don't think I'd go
the each_pod route. Who knows what I'm going to think tomorrow.

Cheers,
Bob
 
J

Jim Freeze

Wow, thanks for all the responses.

First, duh, yeah, I got it backwards (pod and peas should be reversed.)

So, from what I have read, we have:

1. Pod.new.each { |pea| ... }
2. Pod.new.peas.each { |pea| ... }

#2 compromises the OO integrity. I understand that, but
I guess I am bugged by #1 where Pod.new.each yields a 'pea'.
If I see a Pod.each, I kind of expect to get a 'pod' back, not
a pea. That is the reason I did #each_pod.

Any comments on this?
 
D

David A. Black

Hi --

This may be a silly design question, but I always balk at
the right answer when I am confronted with it.

I have a class that manages a list and users need to iterate over that list.
The way I see it, I have to basic alternatives:

# Give user access to the array and let them iterate over Array
class Pea
attr_reader :pods
end
Pea.new.pods.each { |pod| ..do stuff.. }

or

# Provide a custom iterator
class Pea
def each_pod

Why not just call that #each? (And, as others have said, possibly
include Enumerable, though only if you need the other Enumerable
stuff).


David
 
D

David A. Black

Hi --

Wow, thanks for all the responses.

First, duh, yeah, I got it backwards (pod and peas should be reversed.)

So, from what I have read, we have:

1. Pod.new.each { |pea| ... }
2. Pod.new.peas.each { |pea| ... }

#2 compromises the OO integrity. I understand that, but
I guess I am bugged by #1 where Pod.new.each yields a 'pea'.
If I see a Pod.each, I kind of expect to get a 'pod' back, not
a pea. That is the reason I did #each_pod.

Any comments on this?

I think if you're using pod/peas as a container/elements example, then
you should accept that the container contains the elements :)

pod.each {|pea| ... }

has enough pod/pea semantics to make it clear. In fact, this:

pod.each_pea {|pea| ... }

seems a bit redundant to me. (Unless there's something else that a
pod might iterate over, similar to String#each vs. String#each_byte.
Of course, lots of people wish that String#each did what
String#each_byte does :)


David
 
J

Jim Freeze

Why not just call that #each? (And, as others have said, possibly
include Enumerable, though only if you need the other Enumerable
stuff).

Good question.

1) Pod.each { |pea| ... }. There is no indication what each will yield.
2) In some cases, there are multiple things that can be iterated through.
Maybe a better example:

Car.new.each_wheel { |wheel| ... }
Car.new.each_headlight { |light| ... }

Which directs me to do something like:
Car.new.wheels.each ...
Car.new.headlights.each ...

which is back to the un-encapsulation that has been previously mentioned.
 
J

Jim Freeze

I think if you're using pod/peas as a container/elements example, then
you should accept that the container contains the elements :)

pod.each {|pea| ... }

has enough pod/pea semantics to make it clear. In fact, this:

pod.each_pea {|pea| ... }

seems a bit redundant to me. (Unless there's something else that a
pod might iterate over, similar to String#each vs. String#each_byte.
Of course, lots of people wish that String#each did what
String#each_byte does :)

Ok, so you convinced me. Use #each. But, there still does not seem
to be a clear answer for an object having multiple containers.
Granted, there is precedence with things like #each_byte. But,
I wonder if it is better to use an argument to each.
Here are four methods that I can see as potential solutions:

1. obj.each:)container) { |element| ... }
2. obj.each_container { |element| ... }
3. obj.container.each { |element| ... }
4. obj.container; obj.each { |element| ... }

No. (4) simply sets an internal state variable to indicate which container
#each uses. Not pretty, and not thread safe, but permits Enumerable to
be used.

Do you have a preference for 1-4 or another solution?
 
E

Eric Mahurin

--- Jim Freeze said:
=20
Good question.
=20
1) Pod.each { |pea| ... }. There is no indication what each
will yield.
2) In some cases, there are multiple things that can be
iterated through.
Maybe a better example:
=20
Car.new.each_wheel { |wheel| ... }
Car.new.each_headlight { |light| ... }
=20
Which directs me to do something like:
Car.new.wheels.each ...
Car.new.headlights.each ...
=20
which is back to the un-encapsulation that has been
previously mentioned.

I still think the second way is just fine if you just say in
the docs that #wheels and #headlights returns an Enumerable.=20
Or if you really wanted to make sure there is no abuse, just
make #wheels and #headlights return an Enumerator (naming
@wheels.each and @headlights.each as the each methods). Or if
you wanted to be v1.9-like make #each_wheel and #each_headlight
return an Enumerator when no block is supplied.



=09
__________________________________=20
Yahoo! Mail - PC Magazine Editors' Choice 2005=20
http://mail.yahoo.com
 
J

Jim Freeze

Here are four methods that I can see as potential solutions:

1. obj.each:)container) { |element| ... }
2. obj.each_container { |element| ... }
3. obj.container.each { |element| ... }
4. obj.container; obj.each { |element| ... }

No. (4) simply sets an internal state variable to indicate which containe= r
#each uses. Not pretty, and not thread safe, but permits Enumerable to
be used.

I am curious what people think about these methods.
To all you pattern experts, is there a pattern for this
situation?
 
A

Adam Van Den Hoven

Let me suggest yet another possibility, borrowing from Hash:

obj.each { | element, container | ... }

You could even combine that with 1?

I like Mine and #1 .
 
R

Robert Klemme

4. is definitely a non option. That's in the same league as containers
glued together with an iterator (not #each). That's simply a don't do.
It's thread unsafe and error prone. I strongly favor 3 with an optional
variant of a read only proxy returned (using Enumerator for example). 2 is
ok also and maybe 1, too. Normally OO suggests to have separate methods but
since you can easily say this in Ruby 1 seems ok, too:

def each(cont, &b)
instance_variable_get("@#{cont}").each(&b)
self
end

IOW you don't need to touch this method when you add more containers.
I am curious what people think about these methods.
To all you pattern experts, is there a pattern for this
situation?

Not that I'm a pattern expert... I'll throw in my 0.02EUR anyway:

- My rule of thumb, if the object's main task is to be a container, give it
an each method - this makes usage for clients convenient (example TreeNode,
each will iterate child nodes).

- If there are several containers contained :) then make them accessible
and let clients work with them. You save yourself a lot of hassle and name
collisions (KISS).

- Remember: there's no way to protect the internals of an instance: if
someone wants to screw up your instance he can always use send,
instance_eval, instance_variable_get and *_set to access your innards.

Kind regards

robert
 
J

Jim Freeze

- My rule of thumb, if the object's main task is to be a container, give= it
an each method - this makes usage for clients convenient (example TreeNod= e,
each will iterate child nodes).

- If there are several containers contained :) then make them accessibl= e
and let clients work with them. You save yourself a lot of hassle and na= me
collisions (KISS).

Thanks Robert. That's good advice.
 
P

Phil Tomson

Wow, thanks for all the responses.

First, duh, yeah, I got it backwards (pod and peas should be reversed.)

So, from what I have read, we have:

1. Pod.new.each { |pea| ... }
2. Pod.new.peas.each { |pea| ... }

#2 compromises the OO integrity. I understand that, but
I guess I am bugged by #1 where Pod.new.each yields a 'pea'.
If I see a Pod.each, I kind of expect to get a 'pod' back, not
a pea. That is the reason I did #each_pod.

Any comments on this?


what if youcalled the iterator function each_pea?

Pod.new.each_pea {|pea| ... }

Of course then certain Enumerable magic won't work...

But if you consider that a Pod is a container for Peas then even the 'each'
iterator makes sense, I think.

Phil
 
F

Florian Frank

Jim said:
Ok, so you convinced me. Use #each. But, there still does not seem
to be a clear answer for an object having multiple containers.
Granted, there is precedence with things like #each_byte. But,
I wonder if it is better to use an argument to each.

I think people should require 'enumerator' for this, it creates
Kernel#enum_for, so you can create a Enumerable::Enumerator instance
for every iterator:

somethings = obj.enum_for:)each_something)

Then you can iterate

somethings.each do |something|
# ...
end

and because Enumerator includes Enumerable, you can get an
array as well by calling

somethings_array = somethings.to_a

without breaking encapsulation.

This solves all your problems, and you only have to create an arbitrary
named iterator method. I think enumerator should become part of Standard
Ruby, because it's so damn useful.
 
J

James Edward Gray II

I think enumerator should become part of Standard Ruby, because
it's so damn useful.

I believe that's the plan. I think all the standard iterators are
going to start returning enumerator objects when called without a block.

James Edward Gray II
 
G

gabriele renzi

James Edward Gray II ha scritto:
I believe that's the plan. I think all the standard iterators are
going to start returning enumerator objects when called without a block.

James Edward Gray II

would'nt it make more sense to have yield work coroutinish when called
withouth a block, basically integrating the enumerator functionality in it?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
474,183
Messages
2,570,968
Members
47,524
Latest member
ecomwebdesign

Latest Threads

Top