A ruby course

  • Thread starter Brian Schröder
  • Start date
H

Henrik Horneber

Hi!

Taking over from Brian (sorry :) ) I need to ask a question of my own.

Henrik Horneber wrote: ....

Hello Henrik,
....

Extending string with something like remove_cpp_comments seems awkward
to me. In any usecase for this I'd rather have a class CppCode that I
can ask to remove_comments. (Or even better, to give me the source
without comments.)

So my personal heuristic tells me not to extend in this case.

Now, the reason I was immediatly reminded of that remove_cpp_comments
example is, that I wrote a small script some time ago, which used
exactly this case. First, I implemented remove_cpp_comments in its own
class (which was not called CppCode, but had the same intend), but out
of curiosity (and because in ruby I can, dang it! :) ) I moved the
remove_cpp_comments method to String. Turns out, the method got shorter
and somewhat clearer, at least to me. And I could write something like

source_code = IO.readlines(file).join("\n")
source_code.strip_comments!
# instead of source_code = CppCode.strip_comments( source_code)


.... you get the idea.

Even though the code became clearer on that level, I still have to agree
with Brian that it is somehow dirty/awkward on a different level to
extend String with such a method.

This, formerly extremly happy, Ruby user is confused.

What are your experiences?

regards,

Henrik
 
E

Edgardo Hames

Now, the reason I was immediatly reminded of that remove_cpp_comments
example is, that I wrote a small script some time ago, which used
exactly this case. First, I implemented remove_cpp_comments in its own
class (which was not called CppCode, but had the same intend), but out
of curiosity (and because in ruby I can, dang it! :) ) I moved the
remove_cpp_comments method to String. Turns out, the method got shorter
and somewhat clearer, at least to me. And I could write something like

source_code = IO.readlines(file).join("\n")
source_code.strip_comments!
# instead of source_code = CppCode.strip_comments( source_code)

.... you get the idea.

Even though the code became clearer on that level, I still have to agree
with Brian that it is somehow dirty/awkward on a different level to
extend String with such a method.

I usually try not to do that kind of things. Then, for each new
lanaguage you are going to support, you should now add a
remove_#{lang}_comments to String. But then, when reading a given
source file you could remove comments that are not so in that
language.

Regards,
Ed
 
J

James Edward Gray II

You could extend Integer with a fibonacci method, so that you can
write something like:

puts 0.fibonacci > 0
puts 1.fibonacci > 1
puts 5.fibonacci > 5

There was an excellent example along these lines in the RubyConf '04
blogs. According to Francis Hwang's blog:

"Also, Rich [Kilmer] described a cool little hack: Adding time-unit
methods to Fixnum so you can write code like time_to_restart = 5.hours
+ 15.minutes. Dude, I'm totally stealing that."

I have to agree with the entry, it's a killer trick.

James Edward Gray II
 
E

Edgardo Hames

You could extend Integer with a fibonacci method, so that you can
write something like:

"Also, Rich [Kilmer] described a cool little hack: Adding time-unit
methods to Fixnum so you can write code like time_to_restart = 5.hours
+ 15.minutes. Dude, I'm totally stealing that."

That's right, I meant adding the #fibonacci method to Fixnum.

Ed
 
H

Henrik Horneber

Edgardo said:
I usually try not to do that kind of things. Then, for each new
lanaguage you are going to support, you should now add a
remove_#{lang}_comments to String. But then, when reading a given
source file you could remove comments that are not so in that
language.

Regards,
Ed

In my case there is never going to be a different language to support,
but in general, that is a good reason not to do it. I'd like to get away
from my example and to a little wider discussion of when to extend
existing classes. I could have written my own subclass of String called
CppSource, so I could have written

source_code = CppSource.new( IO.readlines(file).join("\n") )
source_code.strip_comments!

which might actually be a better solution than to put strip_comments!
into String. On the other hand (again stealing from the other thread),
writing

time_to_restart = TimeNumeric.new(5).hours + TimeNumeric.new(15).minutes

is just what we would like to avoid. So, as a guideline, should you only
extend an existing class when you use lots of literals in your code?

regards,

Henrik
 
B

Brian Schröder

Edgardo said:
Taken from the Design Patterns book, Decorator pattern:

"Attach additional responsibilities to an object dynamically.
Decorators provide a flexible alternative to subclassing for extending
functionality."

You could extend Integer with a fibonacci method, so that you can
write something like:

puts 0.fibonacci > 0
puts 1.fibonacci > 1
puts 5.fibonacci > 5 Should be 12, shouldn't it?

My 2 cents.
Ed

I'm sorry, but this was exactly the example I gave. It seems we have a
similar mindset ;)

Regards,

Brian
 
M

Markus

On Oct 5, 2004, at 1:20 PM, Edgardo Hames wrote:
There was an excellent example along these lines in the RubyConf '04
blogs. According to Francis Hwang's blog:

"Also, Rich [Kilmer] described a cool little hack: Adding time-unit
methods to Fixnum so you can write code like time_to_restart = 5.hours
+ 15.minutes. Dude, I'm totally stealing that."

I have to agree with the entry, it's a killer trick.

Here's something I've used from time to time (not completely fleshed
out, since I've just been implementing what I need each time I use it).
It lets you write things like:

1.0[kilometers]

1.0[kilometers][miles]

1.0[seconds][hours]

1.0[miles/hours]

1.0[kilometers/second][miles/hour]

1.0[kilometers] + 1.0[miles]

...and does all the right stuff for you. The n[kilometers][miles] form,
for example, gives you the distance in miles equal to n kilometers.

-- Markus

P.S. RubyLicence, GPL, your choice, yada yada


require '/usr/local/ruby/1.8/extensions.rb'
#
# By Markus (MQR) Roberts, 2003+/-1
#
class A_component_vector < Hash
#
# Because the keys are dimensions that themselves contain component
# vectors, we don't want to deep copy past here.
#
def deep_copy
clone
end
end

$known_dimensions = {}
class A_dimension
attr_accessor :name
def initialize(name)
@name = name
end
def components
A_component_vector.new(self=>1.0)
end
def *(other)

A_derived_dimension.new("#{self.name}*#{other.name}",self.components+other.components)
end
def /(other)

A_derived_dimension.new("#{self.name}/#{other.name}",self.components-other.components)
end
def to_str
@name
end
end

class A_derived_dimension < A_dimension
attr_reader :components,:hash
def initialize(name,components)
super(name)
@components = components
#@hash = 0
#@components.each_pair { |k,v| @hash += v*k.hash }
@hash = @components.inject(0) { |x,p| x+p[1]*p[0].hash }
end
def ==(other)
self.class == other.class and
@hash == other.hash and
@components.length == other.components.length and
@components.all? { |k,v|
v == other.components[k]
}
end
end

def define_dimension(name,components=nil)
name = name.to_s.intern
$known_dimensions[name] = components ?
A_derived_dimension.new(name,components.components) :
A_dimension.new(name)
#eval "#{name.to_s.capitalize} = $known_dimensions[#{name.inspect}]"
eval %Q{
def #{name}
$known_dimensions[#{name.inspect}]
end
}
end

define_dimension :duration
define_dimension :distance
define_dimension :area, distance*distance
define_dimension :speed, distance/duration
#define_dimension :velocity, displacement/duration

$known_units = {}
class A_unit
attr_accessor :what,:how_much,:name
def initialize
end
def A_unit.to_measure(what,name,conversion=nil)
result = A_unit.new
result.name = name
conversion = 1.0 unless conversion
conversion = conversion.value*conversion.units.how_much if
conversion.is? A_measure
$known_units[what] ||= {}
$known_units[what][result] = conversion
result.what = what
result.how_much = conversion
result
end
def *(other)

A_unit.to_measure(self.what*other.what,"(#{self.name})*(#{other.name})",self.how_much*other.how_much)
end
def /(other)

A_unit.to_measure(self.what/other.what,"(#{self.name})/(#{other.name})",self.how_much/other.how_much)
end
def to(units)
return 1.0 if units == self
fail "Can't coerce #{self.what.name} to #{units.what.name}" if
self.what != units.what
fail "Can't coerce #{self} to #{units}" if self.what !=
units.what
return self.how_much/units.how_much
end
def to_s
@name #$known_units[@what]
end
def to_str
@name #$known_units[@what]
end
end

class A_measure
attr_reader :value,:units
def initialize(value,units)
@value = value
@units = units
end
def [](units)
p "Trying to convert #{self} to #{units}\n"
A_measure.new(@units.to(units)*@value,units)
#@units.to(units)*@value
end
def *(other)
case other
when A_measure then
A_measure.new(@value*other.value,@units*other.units)
else A_measure.new(@value*other,
@units )
end
end
def /(other)
case other
when A_measure then
A_measure.new(@value/other.value,@units/other.units)
else A_measure.new(@value/other,
@units )
end
end
def +(other)
A_measure.new(@value+other[@units].value,@units)
end
def -(other)
A_measure.new(@value-other[@units].value,@units)
end
def to_s
"#{value}[#{units}]"
end
def to_str
"#{value}[#{units}]"
end
end

class Numeric
def [](units)
A_measure.new(self,units)
end
end

def define_unit_of(what,names,comparison=nil)
names = [names,names.to_s+'s'] unless names.is_a? Array
names.each { |name|
eval %Q{
def #{name}(quantity=nil)
if quantity
A_measure.new(quantity,#{name})
else

A_unit.to_measure(#{what.name},"#{name}",#{comparison ? comparison :
'nil'})
end
end
}
}
end

def define_metric_unit_of(what,name,comparison=nil)
define_unit_of what,name,comparison
define_unit_of what,'nano' +name.to_s,1e-9[name]
define_unit_of what,'micro'+name.to_s,1e-6[name]
define_unit_of what,'milli'+name.to_s,1e-3[name]
define_unit_of what,'centi'+name.to_s,1e-2[name]
define_unit_of what,'deci' +name.to_s,1e-1[name]
define_unit_of what,'deka' +name.to_s,1e+1[name]
define_unit_of what,'kilo' +name.to_s,1e+3[name]
define_unit_of what,'mega' +name.to_s,1e+6[name]
define_unit_of what,'giga' +name.to_s,1e+9[name]
end

define_metric_unit_of distance, :meter
define_unit_of distance, [:foot,:feet], 0.3048[meters]
define_unit_of distance, :mile, 1609.344[meters]
define_metric_unit_of duration, :second
define_unit_of duration, :minute, 60.0[seconds]
define_unit_of duration, :hour, 60.0[minutes]

fail "Type checking doesn't work!" unless begin
print "#{1.0[kilometers] + 1.0[seconds]}\n"
false
rescue
true
end

print "#{1.0[kilometers]}\n"
print "#{1.0[kilometers][miles]}\n"
print "#{1.0[seconds][hours]}\n"
print "#{1.0[miles/hours]}\n"
print "#{1.0[kilometers/second][miles/hour]}\n"
print "#{1.0[kilometers] + 1.0[miles]}\n"
 
E

Edgardo Hames

Should be 12, shouldn't it?

0.fib > 0
1.fib > 1
2.fib > 1
3.fib > 2
4.fib > 3
5.fib > 5
I'm sorry, but this was exactly the example I gave. It seems we have a
similar mindset ;)

I'm really sorry, I must admit I didn't really paid much attention to
the example. I'm replying messages while in a training session... :(

But anyway, you can think of the Visitor or Decorator pattern, for
extending classes. Did I mention adding a fib method to Fixnum? ;)

Regards,
Ed
 
B

Brian Schröder

Edgardo said:
0.fib > 0
1.fib > 1
2.fib > 1
3.fib > 2
4.fib > 3
5.fib > 5

Oh, 12 was the sum of the fibonaccy numbers up to 5. Too much thinking
is bad for your brain.
 
L

Leo

Brian said:
Hello Group,

I need more help.

Anybody has an idea for an exercise on extending existing classes.

The code below introduce a new string concatenation operator '/' which
concatenates two strings placing '/' beween them. This is useful
when forming path from directory and filename components.

class String
def /(rhs)
self.sub(%r{/+$}, '') + '/' + rhs.sub(%r{^/+}, '')
end
end if

Now you can write the following code:

$dir='/usr/local/bin'
$name='ruby'
$full_path= $dir/$name

Also the '/' operator will make sure that the strings are joined by one and
only one slash. Example:
$dir='/badby/formed/dir///'

$name='///foo'

$dir/$name => '/badby/formed/dir/foo'

Note, that poatterns like '///' were removed.

Hope it helps,
Leo
 
B

Brian Schröder

Leo said:
Brian Schröder wrote:




The code below introduce a new string concatenation operator '/' which
concatenates two strings placing '/' beween them. This is useful
when forming path from directory and filename components.

class String
def /(rhs)
self.sub(%r{/+$}, '') + '/' + rhs.sub(%r{^/+}, '')
end
end if

Now you can write the following code:

$dir='/usr/local/bin'
$name='ruby'
$full_path= $dir/$name

Also the '/' operator will make sure that the strings are joined by one and
only one slash. Example:
$dir='/badby/formed/dir///'

$name='///foo'

$dir/$name => '/badby/formed/dir/foo'

Note, that poatterns like '///' were removed.

Hope it helps,
Leo

That is short and cool. I think this could be an example for me!

Thanks,

Brian
 
E

Edgardo Hames

Oh, 12 was the sum of the fibonaccy numbers up to 5. Too much thinking
is bad for your brain.

What are you talking about? As far as I know the fibonacci series is defined as

fib n = fib (n-1) + fib (n-2)

Then,
fib 5 =
= fib 4 + fib 3
= (fib 3 + fib 2) + (fib 2 + fib 1)
= ((fib 2 + fib 1) + (fib 1 + fib 0)) + (fib 1 + fib 0) + (1+0)
= ((fib 1 + fib 0) + (1+ 0))+ (1+0) + (1+0) + (1+ 0)
= 1+1+1+1+1
= 5

You might cosider taking a look at
http://mathworld.wolfram.com/FibonacciNumber.html
It defines fib 0 = 0, but either way, 12 doesn't belong to the series.

Regards,
Ed
 
B

Brian Schröder

Edgardo said:
What are you talking about? As far as I know the fibonacci series is defined as

fib n = fib (n-1) + fib (n-2)

Then,
fib 5 =
= fib 4 + fib 3
= (fib 3 + fib 2) + (fib 2 + fib 1)
= ((fib 2 + fib 1) + (fib 1 + fib 0)) + (fib 1 + fib 0) + (1+0)
= ((fib 1 + fib 0) + (1+ 0))+ (1+0) + (1+0) + (1+ 0)
= 1+1+1+1+1
= 5

You might cosider taking a look at
http://mathworld.wolfram.com/FibonacciNumber.html
It defines fib 0 = 0, but either way, 12 doesn't belong to the series.

Regards,
Ed
I meant "bad for _my_ brain". Sorry if I have confused you. You were
absolutely right, and I calculated the wrong thing.

Regards,

Brian
 
M

Mark Hubbart

In my case there is never going to be a different language to support,
but in general, that is a good reason not to do it. I'd like to get away
from my example and to a little wider discussion of when to extend
existing classes. I could have written my own subclass of String called
CppSource, so I could have written

source_code = CppSource.new( IO.readlines(file).join("\n") )
source_code.strip_comments!

which might actually be a better solution than to put strip_comments!
into String. On the other hand (again stealing from the other thread),
writing

time_to_restart = TimeNumeric.new(5).hours + TimeNumeric.new(15).minutes

is just what we would like to avoid. So, as a guideline, should you only
extend an existing class when you use lots of literals in your code?

imho, there are two occasions for extending a class:
1. When the added methods are useful for that class in general (ie,
String#rot13, Integer#factors)
2. When you're just hacking :)

If the data that you are working with is sort of an extended version
of the class, that's when I would subclass. I think the common text
markup modules are excellent examples of this; redcloth and bluecloth
both act just like strings, but the have a few important methods that
treat them as specific data types.

In this vein, TimeNumeric might be subclassed to use like this:
# TimeNumeric < Float
... where the float value is the number of seconds.

time_to_shutdown = TimeNumeric.new(5*60*60 + 15*60)
or better yet:
time_to_shutdown = TimeNumeric.hours(5.25)
or even:
time_to_shutdown = TimeNumeric["5:15:00"] # calls "#to_time_numeric"
on the string

cheers,
Mark
 
F

Francis Hwang

imho, there are two occasions for extending a class:
1. When the added methods are useful for that class in general (ie,
String#rot13, Integer#factors)
2. When you're just hacking :)

But this is all fairly subjective, isn't it? Among some programmers,
String#rot13 might be a really sensible thing to add, but to others
maybe it might seem like cruft. One interesting example from RubyConf
was Rich Kilmer saying that he added methods like #minutes and #days to
Fixnum, so he could say things like

15.minutes + 2.hours

It's also very easy to imagine using this for, say, feet and inches:

1.miles + 6.feet + 6.inches

... which is culturally specific, isn't it? I mean, you're not going to
use that if you use the metric system like a civilized people.

There are two competing philosophies in OO theory. On one hand, you
have the Platonists who believe that types are eternal forms that can
be found in nature, so if you did your analysis right you'd find those
forms and be able to codify them in your class definitions. On the
other hand, you have the Pragmatists who believe that types are just
shorthand, to be defined and discarded as the situation allows.

Ruby is more of a Pragmatist language, which personally I like 'cause
on some deep level it jibes with my own belief in the subjectivity of
experience. So, you know, if you want to extend a class, do it if it
makes your life easier. If it doesn't, then don't do it.

Francis Hwang
http://fhwang.net/
AIM: francisrhizome
 
G

Gavin Sinclair

Hello Group,
I need more help.
Anybody has an idea for an exercise on extending existing classes. So
far the only thing I have come up with is:
\exercise{Fibonacci II}{
Extend \codetype{Integer} with a function fib that calculates the
corresponding fibonacci number.}
And my creativity is at an all time low.

There are plenty of examples at http://extensions.rubyforge.org. I
was thinking of something like String#indent as an exercise, but you
haven't taught regexen at this point, so that's out. But something
else might do.
Anybody nows of a good example where it is neccessary to extend an
existing object?
I have never used this feature and I don't know when to use it in a
sensible way.

It could be something academic like changing the to_s output for a
particular array/string. But really, the main point of this feature
(adding methods to objects) is to add methods to classes (which are
objects). So perhaps use that as an example.
Somebody has an idea for an exercise on using modules (as namespace and
mixin?)

A favourite of mine is building a new class, then mixing in Enumerable
and Comparable. So you're not building a new module, but reusing two
existing ones.

Gavin
 
G

Gavin Sinclair

I'm sorry, but this was exactly the example I gave. It seems we have a
similar mindset ;)

Maybe extend this, then:

module InlineMath
def fib; ...; end
def fac; ...; end
def sin; ...; end
def cos; ...; end
def tan; ...; end
end

class Numeric
include InlineMath
end

Students have to implement those methods, and try it out with real
numbers. Caveat: what about 3.5.fib and 3.5.fac? Do the
trigonometric methods assume degress or radians?

Gavin
 
W

Will Drewry

There are plenty of examples at http://extensions.rubyforge.org. I
was thinking of something like String#indent as an exercise, but you
haven't taught regexen at this point, so that's out. But something
else might do.

A question someone asked earlier on ruby-talk might be an easy, but
appropriate extension to go with #indent and others:
-- String#pack:

class String
def pack(arg)
self.split(//).pack(arg)
end
end


Good luck!
/wad
 
M

Mark Hubbart

But this is all fairly subjective, isn't it?

Extremely :) In fact, my first draft of the email said so... but I
revised it, and apparently left that statement out.

What I was getting at here, is that #rot13 could be reasonably applied
to any text; and that is the primary use of strings in ruby: a text
container. Whereas, #remove_html_attributes would only be used on
special types of text: html documents/snippets. In this case, I would
subclass; in the rot13 case, I would extend. But that's just me, you
might feel differently. :)
Among some programmers,
String#rot13 might be a really sensible thing to add, but to others
maybe it might seem like cruft. One interesting example from RubyConf
was Rich Kilmer saying that he added methods like #minutes and #days to
Fixnum, so he could say things like

15.minutes + 2.hours

It's also very easy to imagine using this for, say, feet and inches:

1.miles + 6.feet + 6.inches

... which is culturally specific, isn't it? I mean, you're not going to
use that if you use the metric system like a civilized people.

These things are great tricks, and very handy. But the ability to do
this belongs in the hands of the last coder to work on it. They
shouldn't be included in libs, etc, unless the express purpose of the
lib is to add them. They shouldn't be a side effect.

But they can be awfully handy in a script, or in small applications.
There are two competing philosophies in OO theory. On one hand, you
have the Platonists who believe that types are eternal forms that can
be found in nature, so if you did your analysis right you'd find those
forms and be able to codify them in your class definitions. On the
other hand, you have the Pragmatists who believe that types are just
shorthand, to be defined and discarded as the situation allows.

I tend to be more Pragmatic when I'm writing code that others won't be
editing/coding around, and Platonic when I'm writing library code.
Ruby is more of a Pragmatist language, which personally I like 'cause
on some deep level it jibes with my own belief in the subjectivity of
experience. So, you know, if you want to extend a class, do it if it
makes your life easier. If it doesn't, then don't do it.

Ruby *lets* you be pragmatic. That doesn't mean Ruby *itself* conforms
to your definition of pragmatic. Check the library code, and you'll
probably find it to be awfully platonic. And that's the way I like it,
because I often have trouble understanding other people's pragmatic
code :)

Still, as I said, I'm not trying too say you should program any way
you want. my earlier post was prefaced with "imho" for that reason:
It truly was just my opinion, and you can safely ignore it if you want
:)

cheers,
Mark
 
B

Brian Schröder

Brian said:
Hello List,

again I ask for your help. I'm going to give a 16 hours course on ruby
this week, and I've created some slides to aid me with this.
I have to admit that some examples might not be as bright as I wish, and
also there are missing some exercises.
I created the slides in english, such that they can be used by a bigger
part of the community if need should be.

The slides will not cover all that I hope to say in the course, when the
slides have finished I will start with a free experimentation phase
based on my ants sample implementation
http://ruby.brian-schroeder.de/ants/

It would be great if some of you could look at the slides and correct
the language and if somebody comes up with a great example or exercise I
would also be glad for comments.

The slides are located at
http://ruby.brian-schroeder.de/course/

Regards,

Brian


Thank you all for the great discussion and helpfull examples!

Sadly I could not use all the examples, because I have limited time in
the course. But I wanted to acknowledge that people here are always full
of good ideas.

Regards,

Brian
 

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

Members online

Forum statistics

Threads
473,780
Messages
2,569,611
Members
45,265
Latest member
TodLarocca

Latest Threads

Top