A ruby course

> 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 :slight_smile:

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

Extremely :slight_smile: 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. :slight_smile:

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 :slight_smile:

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
:slight_smile:

cheers,
Mark

···

On Wed, 6 Oct 2004 08:52:27 +0900, Francis Hwang <sera@fhwang.net> wrote:

On Oct 5, 2004, at 5:22 PM, Mark Hubbart wrote:

There are a couple of caveats to extending or subclassing built-in classes
though:

(1) There is no way to 'coerce' an object of class X into an instance of
class Y, where Y is a subclass of X. So subclassing built-in types relies on
the existence of a constructor which takes an instance of an existing
object. e.g.

  class HTMLString < String
    ...
  end

  foo = HTMLString.new("<html>hello</html>")

In this case, HTMLString#initialize(str) can call String#initialize(str)
- implicitly in this case, or explicitly using 'super'. Or it could use the
'replace' method:

  foo = HTMLString.new
  foo.class # => HTMLString
  foo.replace("abc")
  foo.class # => HTMLString

But this makes me a bit uncomfortable because no class is *required* to have
an initializer which takes an existing instance of the same class, nor a
'replace' method. And when you're talking about a built-in class, the value
is not accessible via an instance variable. So, in theory at least, this
approach might not work at all.

Consider subclassing Range, for example; you have to be careful to pass the
right bits to the superclass constructor, if you want to allow an existing
Range or MyRange object to be passed in.

  class MyRange < Range
    def initialize(r)
      super(r.begin, r.end, r.exclude_end?)
    end
  end

(2) If you're running multiple applications within a shared interpreter,
such as mod_ruby, then clearly you don't want these applications to step on
each others' toes, so leaving the core classes alone is usually a good
principle.

I personally prefer two alternative strategies:

(a) using the singleton class of objects

  module HTMLString
    def flatten
      gsub(/<[^>]*>/,' ')
    end
  end

  a = "<html>hello world</html>"
  a.extend HTMLString
  a.flatten # => " hello world "

Now string 'a' has this method, but we've not polluted String. This is the
nearest to coercion that I can find in Ruby; don't change the actual class,
but mix in something else.

(b) using delegation ("has_a") rather than inheritance ("is_a"). It's often
a hands-down win in terms of flexibility and clarity anyway. For a
HTMLString object, I would write something like

  class HTMLString
    def initialize(str)
      @str = str
    end
    def flatten
      @str.gsub(/<[^>]*>/,' ')
    end
  end

Working with an instance variable gives more control over which methods you
wish to delegate, which you wish to hide (e.g. if they make no sense for
this new class), and which to make behave differently; and you can easily
compose objects out of two or more other objects this way.

Regards,

Brian.

···

On Thu, Oct 07, 2004 at 04:13:19AM +0900, Mark Hubbart wrote:

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. :slight_smile: