Ruby and OOP-design (question of an old "procedural person" ;)

Hi !

I have a newbie question on OOP-design:

What I have is a large ASCII-File, a list of about 11000 shortwave
broadcasters. Each line contains the informations about one
transmitter hold in a long string without any delimiters.

It is specified, what offsets into a line are to be used to cut off
the informations one would need about a broadcaster.

What I want to do is to read the whole file into memory, put each
information into a sperate “field” to get rid of those
"information-one-liners" and then I want to sort the whole thing by
frequency first and by on-air-time then.

What I done so far is (pseudo-code):

···

class SWFILE( file)
open the file
@ist=List.new
foreach line do
format line by expressions like @time=line[…]
transmitter=Broadcaster.new( @freq, @station, @time…)
List.append( transmitter )
end foreach
end

class Broadcaster
def initialize( freq, station, time…)
@freq = freq
@station = station
@time = time
…
end
end

class List
def initialize
@list=Array.new
end

def append( broadcaster)
@list.push( broadcaster)
end
end

a=SWFILE.new( “” )


This works so far…

Then I read about the new sort_by feature. This is exactly what I
need!

But sort_by needs an array as input.

And my “list”-object is an object (or a duck, which quaks :wink: holding
and array…or what…or … I got confused here.

Furthermore I am really not sure, whether I have generated as
"class-ified" program, which is programmed in a procedural way only
or … ?

Two questions:
What I have to feed where into the structure to sort the broadcasters
by frequency first and then by on-air-time ?

Is this “design” ok (in the sense of OOP)?

Kind regards from a confused duck, which quaks,
Meino

Then I read about the new sort_by feature. This is exactly what I
need!

But sort_by needs an array as input.

And my “list”-object is an object (or a duck, which quaks :wink: holding
and array…or what…or … I got confused here.

class List
def sort!
@list = @list.sort_by { |x| [x.freq, x.time] }
end
end

In other words, since you’ve wrapped Array in your List object, then you
give your List object a sort! method which sorts itself. This is an example
of the “has_a” / “delegation” design pattern: List contains (has_a) Array
instance, and List#sort passes on (delegates) the sort request to the object
it contains.

This is an excellent way of composing systems, but in this simple case you
might just want to use an Array directly and drop the List class. It depends
how much extra functionality you want to bundle in there. If there’s not
much in ‘List’ on top of a standard Array, an alternative is to make class
methods which perform the specific operations you want on an array of your
special objects:

class Broadcaster
def Broadcaster.sort_by_freq(arr)
arr.sort_by { |x| [x.freq, x.time] }
end
end

stations = # generic array
stations << Broadcaster.new(…)
…
stations = Broadcaster.sort_by_freq(stations)

Is this “design” ok (in the sense of OOP)?

I’d say it looks pretty sound (no pun intended!)

Regards,

Brian.

···

On Sat, Aug 09, 2003 at 04:47:10PM +0900, Meino Christian Cramer wrote:

But sort_by needs an array as input.

No, sort_by is a method of Enumerable and Enumerable is a mixin that can
be included in every class that implements the each method.

Two questions:
What I have to feed where into the structure to sort the broadcasters
by frequency first and then by on-air-time ?

There are many possibilties. You could use sort_by as you said
(escpecially if you want to use a different order at different
occasions) or you could mixin Comparable into your Broadcaster class and
let the Broadcaster objects decide on their own how to compare to each
other.

Is this “design” ok (in the sense of OOP)?

If you want to define one order that should be used for every
broadcaster, the Comparable version is more OOP because the Broadcaster
objects have the responsibility to compare to each other. If you want
more than one order it’s perhaps better if the container class has that
responsibility.

class Broadcaster

include Comparable

def initialize(freq, station, time)
    @freq, @station, @time = freq, station, time
end

attr_reader :freq, :station, :time

def <=>(other)
    (self.freq <=> other.freq).nonzero? || other.time <=> self.time 
end

def to_s
    "<#@station: #@freq #@time>"
end

end

class List

include Enumerable

def initialize
    @list = Array.new
end

def append(broadcaster)
    @list.push(broadcaster)
end

def each(&block)
    @list.each(&block)
end

end

alphabet = (“A”…“Z”).to_a
list = List.new
10.times do
list.append Broadcaster.new(
90.66 + rand(10),
(1…4).map { alphabet[rand(alphabet.size)] }.join,
rand(10000)
)
end

puts “sort_by:”
puts list.sort_by { |x| [ x.freq, -x.time ] }
puts “sort:”
puts list.sort

···

On 2003-08-09 16:47:10 +0900, Meino Christian Cramer wrote:

–
Programs must be written for people to read, and only incidentally for
machines to execute.
– Abelson/Sussman, The Structure and Interpretation of Computer Programs

Hi Brian !

*** Thank you very much for your mail and your kind words! ***

After I saw your solution, I got the flat-hand-on-the-forehead-effect :wink:

I got stuck in “after the object generation” and could only
thought of “TRAPPED!”… :slight_smile:

But now it is clear, what I have to do!

Thank you very much indeed!
Kind regards and have a nice weekend!
Meino

···

From: Florian Frank flori@nixe.ping.de
Subject: Re: Ruby and OOP-design (question of an old “procedural person” :wink:
Date: Sat, 9 Aug 2003 17:50:09 +0900

Shouldn’t that be

def <=>(other)
a = (self.freq <=> other.freq)
a.nonzero? ? a : (other.time <=> self.time)
end

since calling nonzero? collapses [-1,1] to ‘true’.

martin

···

Florian Frank flori@nixe.ping.de wrote:

def <=>(other)
(self.freq <=> other.freq).nonzero? || other.time <=> self.time
end

Hi, :slight_smile:

THANK YOU VERY MUCH FOR THE IMMENS RESPONSE TO MY QUESTION!

I am very happy to be on this very communicative list :o) !

Since I am at the very very beginning of Ruby-Programming please
understand, that I only understand the smallest part of the
discussion…

In the meantime I git a sorted list of “my” shortwave transmitters.

I did:

   @list.push( [freq, stat, time, days, lang, graf, targ, 
               loca, powr, azim,rema, stau, modu, na, ca, 
               sa, eu, af, me, as, au, pa, cira, coco, coun, 
               long, lati, loco, regi, stn , prst, prog] )

for each input line.

Then I did a

   @list.sort_by { |item| [item[0], item[2] ] }

and it works smooth and fast as a ligthning.

The above expressions sorts the broadcasters first on frequency then
on on-air time.

Then I thought of a list first sorted by time and then by frequency
and wrote

   @list.sort_by { |item| [item[2], item[0] ] }

and astonishly Ruby quaks :wink: with :

swlist:62:in `sort_by’: comparison of Array with Array failed (ArgumentError)
from swlist:62

(“swlist” is the name of my ruby-script)

I dont understand that…

I am sure that the bug is sitting in front of my monitor…but…

What is it what I did so badly wrong ? X)

Thank you very much for any help in advance !

Keep being Ruby!
Meino

Martin DeMello wrote:

Shouldn’t that be

def <=>(other)
a = (self.freq <=> other.freq)
a.nonzero? ? a : (other.time <=> self.time)
end

since calling nonzero? collapses [-1,1] to ‘true’.

That was my gut reaction at first but:
$ ruby1.8 -v -e “p( -5.nonzero?, 5.nonzero?, 0.nonzero? )”
ruby 1.8.0 (2003-08-04) [i686-linux]
-5
5
nil

That is the beauty of only nil and false being false…

···

–
([ Kent Dahl ]/)_ ~ [ Kent Dahl - Kent Dahl ]/~
))_student_/(( _d L b_/ (pre-) Master of Science in Technology )
( __õ|õ// ) )Industrial economics and technological management(
_
/ö____/ (_engineering.discipline=Computer::Technology)

Actually, I fancy

def <=>(other)
[self.freq, self.time] <=> [other.freq, other.time]
end

I don’t know if it works, but I fancy it. Given what I know about
Ruby, I’d be surprised if it didn’t work.

Gavin

···

On Saturday, August 9, 2003, 8:31:47 PM, Martin wrote:

Florian Frank flori@nixe.ping.de wrote:

def <=>(other)
(self.freq <=> other.freq).nonzero? || other.time <=> self.time
end

Shouldn’t that be

def <=>(other)
a = (self.freq <=> other.freq)
a.nonzero? ? a : (other.time <=> self.time)
end

since calling nonzero? collapses [-1,1] to ‘true’.

Meino Christian Cramer wrote:

Hi, :slight_smile:

THANK YOU VERY MUCH FOR THE IMMENS RESPONSE TO MY QUESTION!

I am very happy to be on this very communicative list :o) !

Since I am at the very very beginning of Ruby-Programming please
understand, that I only understand the smallest part of the
discussion…

In the meantime I git a sorted list of “my” shortwave transmitters.

I did:

  @list.push( [freq, stat, time, days, lang, graf, targ, 
              loca, powr, azim,rema, stau, modu, na, ca, 
              sa, eu, af, me, as, au, pa, cira, coco, coun, 
              long, lati, loco, regi, stn , prst, prog] )

for each input line.

Then I did a

 @list.sort_by { |item| [item[0], item[2] ] }

and it works smooth and fast as a ligthning.

The above expressions sorts the broadcasters first on frequency then
on on-air time.

Then I thought of a list first sorted by time and then by frequency
and wrote

 @list.sort_by { |item| [item[2], item[0] ] }

and astonishly Ruby quaks :wink: with :

swlist:62:in `sort_by’: comparison of Array with Array failed (ArgumentError)
from swlist:62

(“swlist” is the name of my ruby-script)

I dont understand that…

I am sure that the bug is sitting in front of my monitor…but…

What is it what I did so badly wrong ? X)

Thank you very much for any help in advance !

Keep being Ruby!
Meino

Maybe there’s a problem with one of the times? Since the first sort_by
would look at item[0] first
and only item[2] if both item[0]'s are the same (I think), it might skip
over the problem time value,
while the second wouldn’t be able to;

Maybe try printing out all the item[2] values and looking for something
fishy there? Just a thought.

  • Dan

Then I thought of a list first sorted by time and then by frequency
and wrote

 @list.sort_by { |item| [item[2], item[0] ] }

and astonishly Ruby quaks :wink: with :

swlist:62:in `sort_by’: comparison of Array with Array failed (ArgumentError)
from swlist:62

One or more of your times is nil.

blink

Okay, I’m officially Surprised. Seriously, I don’t see this as a good
thing - ? methods ought to be strictly boolean IMO, and not rely on the
‘value’ of true. Otherwise, some of their self-documenting nature is
lost.

martin

···

Kent Dahl kentda+news@stud.ntnu.no wrote:

That was my gut reaction at first but:
$ ruby1.8 -v -e “p( -5.nonzero?, 5.nonzero?, 0.nonzero? )”
ruby 1.8.0 (2003-08-04) [i686-linux]
-5
5
nil

Yep. In Perl it’s a common idiom to do something like
@a = sort { $a->foo <=> $b->foo || $a->bar <=> $b->bar } @a;
to sort @a because 0 is also false. But the Ruby Way seems to be less
hackish to me than Perl’s approach.

Consider this in Perl:

0 || “false” => “false”

“0” || “false” => “false”

“00” || “false” => “00”

00 || “false” => “false”

The last example interprets 00 as octal 0 and thus false, but the string
“00” is just a string that consists of two chars and it happens to be
true. The string “0” is interpreted as zero none the less. In any case
you could add 1 to both so that a number is returned.

“0” + 1 => 1
“00” + 1 => 1

It’s funny that Ruby looks like Perl but if you go into the details you
realize that it has a much better way to achieve the nice things without
having to carry the nasty things.

···

On 2003-08-09 19:31:49 +0900, Kent Dahl wrote:

That is the beauty of only nil and false being false…

–
Premature optimization is the root of all evil in programming.
– C.A.R. Hoare

So it does! Nice one. One could even imagine something analogous to

class Broadcast
cmp_by :freq, :time

which generates the <=> method, though we’d need to think up some nice
syntax for reversing fields.

martin

···

Gavin Sinclair gsinclair@soyabean.com.au wrote:

Actually, I fancy

def <=>(other)
[self.freq, self.time] <=> [other.freq, other.time]
end

I don’t know if it works, but I fancy it. Given what I know about
Ruby, I’d be surprised if it didn’t work.

THAT’S IT ! THANKS A LOT ! :))

  Meino
···

From: Carlos angus@quovadis.com.ar
Subject: Re: Ruby and OOP-design (question of an old “procedural person” :wink:
Date: Tue, 12 Aug 2003 22:36:10 +0900

One or more of your times is nil.

Okay, I'm officially Surprised. Seriously, I don't see this as a good
thing - ? methods ought to be strictly boolean IMO, and not rely on the

                                          ^^^^^^^
What is this ?

svg% ruby -e 'p Boolean # :-)'
-e:1: uninitialized constant Boolean (NameError)
svg%

Guy Decoux

Martin DeMello wrote:

···

Kent Dahl kentda+news@stud.ntnu.no wrote:

$ ruby1.8 -v -e “p( -5.nonzero?, 5.nonzero?, 0.nonzero? )”
ruby 1.8.0 (2003-08-04) [i686-linux]
-5
5
nil

blink

Okay, I’m officially Surprised. Seriously, I don’t see this as a good
thing - ? methods ought to be strictly boolean IMO, and not rely on the
‘value’ of true. Otherwise, some of their self-documenting nature is
lost.

Not yet on the duck typing train, I see.

The ?-methods are still self-documenting in the sense that they tell you
how you can use the result: as a boolean. That is a type, as in “I may
be used in conditionals”, and not a class, as quite evident by the lack
of the Boolean common ancestor.

–
([ Kent Dahl ]/)_ ~ [ Kent Dahl - Kent Dahl ]/~
))_student_/(( _d L b_/ (pre-) Master of Science in Technology )
( __õ|õ// ) )Industrial economics and technological management(
_
/ö____/ (_engineering.discipline=Computer::Technology)

The superclass manque of TrueClass and FalseClass :slight_smile:

martin

···

ts decoux@moulon.inra.fr wrote:

Okay, I’m officially Surprised. Seriously, I don’t see this as a good
thing - ? methods ought to be strictly boolean IMO, and not rely on the
^^^^^^^
What is this ?

svg% ruby -e ‘p Boolean # :-)’
-e:1: uninitialized constant Boolean (NameError)

Well and good, but they don’t document the fact that the return value
can be used as anything other than a boolean. Duck typing or no, I find
it conceptually messy to have a method called nonzero? return self
rather than true - compare nil? and zero?. Using the value of self feels
like relying on an undocumented side effect. (I feel the same way about
! methods returning nil rather than self when they haven’t made a
change).

martin

···

Kent Dahl kentda+news@stud.ntnu.no wrote:

Not yet on the duck typing train, I see.

The ?-methods are still self-documenting in the sense that they tell you
how you can use the result: as a boolean. That is a type, as in “I may
be used in conditionals”, and not a class, as quite evident by the lack
of the Boolean common ancestor.

Martin DeMello wrote:

Not yet on the duck typing train, I see.

The ?-methods are still self-documenting in the sense that they tell you
how you can use the result: as a boolean. That is a type, as in “I may
be used in conditionals”, and not a class, as quite evident by the lack
of the Boolean common ancestor.

Well and good, but they don’t document the fact that the return value
can be used as anything other than a boolean.

You’re asking alot from a mere method name and a single character. For
my special class, I might be able to use what I recieve from a to_s or
to_str in a special way, and I’ll have to document it. The important
thing NOT to break, is how it works in the more general term.

Consider inheritance: You should comply with the interface of the
superclass for substitutability. However, you may augment and extend it
as long as it is still possible to substitute a parent object with a
child object.

nonzero? just does this using core design decisions of Ruby (nil and
false are only false values) instead of classes.

Duck typing or no, I find
it conceptually messy to have a method called nonzero? return self
rather than true - compare nil? and zero?.

Well, we sure wouldn’t want nil? returning self instead of true. Then it
would only return nil or false. :slight_smile:

As for zero?, I guess it just wasn’t useful enough to return self. Just
what will you do with 0, 0.0, Rational(0,1) or any other zero that is
returned? The nonzero? may return values from a much bigger set, and
thus has lots of useful chaining possibilities.

Using the value of self feels
like relying on an undocumented side effect.

Now I know Ruby is easy to read, but we can’t have all the
documentation be Ruby source code. :slight_smile:
http://www.rubycentral.com/book/ref_c_numeric.html#Numeric.nonzero_qm

(I feel the same way about
! methods returning nil rather than self when they haven’t made a
change).

Are you saying the usage of ? and ! should be restricted to only the two
protocols/types/interfaces? I.e.
? => result either true or false and
! => self if modified, nil if unmodified
That would be a huge blow against the readability potential of Ruby
code, IMHO:

Real life:

  • “Have you found the answer to life, the universe and everything?”
  • “Yeah, it’s 42.”
    Ruby code:
    adams.found_answer? #=> 42
···

Kent Dahl kentda+news@stud.ntnu.no wrote:

–
([ Kent Dahl ]/)_ ~ [ http://www.pvv.org/~kentda/ ]/~
))_student_/(( _d L b_/ (pre-) Master of Science in Technology )
( __õ|õ// ) )Industrial economics and technological management(
_
/ö____/ (_engineering.discipline=Computer::Technology)

Well and good, but they don’t document the fact that the return value
can be used as anything other than a boolean. Duck typing or no, I find
it conceptually messy to have a method called nonzero? return self
rather than true - compare nil? and zero?. Using the value of self feels
like relying on an undocumented side effect.

But as long as it isn’t false or nil, it is true in effect.
And it’s far from undocumented.

Yet in a way I sympathize… where I would feel funny is if
I made use of the numeric value returned rather than just
checking its truth… but I could probably get over even that.

(I feel the same way about
! methods returning nil rather than self when they haven’t made a
change).

There we’re in full agreement. I see the rationale for this, but
I never use the test for nil here (nor have I ever felt the
need to). All this does for me is prevent method chaining in
certain (fairly obscure) circumstances.

Hal

···

----- Original Message -----
From: “Martin DeMello” martindemello@yahoo.com
Newsgroups: comp.lang.ruby
To: “ruby-talk ML” ruby-talk@ruby-lang.org
Sent: Saturday, August 09, 2003 7:52 AM
Subject: Re: Ruby and OOP-design (question of an old “procedural person” :wink:

–
Hal Fulton
hal9000@hypermetrics.com