Forum: Ruby Extending Core Classes - Best Practices

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Pistos C. (Guest)
on 2006-05-05 22:58
Hi, all.

I've been following the "Sharp knives and glue" thread.  It's gotten me
thinking about the things I've already been doing with Ruby.  I wanted
to ask for opinions on when it is good, bad, acceptable to extend core
classes like String, Fixnum, Array, Hash.

To be a bit more concrete: Would it be okay to tack on "is?" and "has?"
type methods?  Contrived example:

class String
    VOGON_VOCABULARY = [
        'freddled', 'gruntbuggly', 'micturations', 'plurdled',
        'gabbleblotchits', 'lurgid', 'groop', 'foonting',
        'turlingdromes', 'hooptiously', 'drangle', 'bindlewurdles',
        'gobberwarts', 'blurglecruncheon',
    ]

    VOGON_REGEXP = /#{VOGON_VOCABULARY.join('|')}/i

    def vogon?
        ( VOGON_REGEXP =~ self ).respond_to? '>'
    end
end

Pistos
Robert K. (Guest)
on 2006-05-06 00:36
(Received via mailing list)
2006/5/5, Pistos C. <removed_email_address@domain.invalid>:
> class String
>         ( VOGON_REGEXP =~ self ).respond_to? '>'
>     end
> end

What exactly do you intend to achieve with ".respond_to? '>'"?

And, btw, this sounds very specialized - it's certainly ok as part of
a script. I wouldn't put it into a lib though.

A simpler solution might be to just do

VOGON === "bluh"

where VOGON is your RX.

Kind regards

robert
Logan C. (Guest)
on 2006-05-06 00:45
(Received via mailing list)
On May 5, 2006, at 4:33 PM, Robert K. wrote:

>>
>>     ]
>>
>>     VOGON_REGEXP = /#{VOGON_VOCABULARY.join('|')}/i
>>
>>     def vogon?
>>         ( VOGON_REGEXP =~ self ).respond_to? '>'
>>     end
>> end
>
> What exactly do you intend to achieve with ".respond_to? '>'"?
>

I think he's trying to make sure the result is a number and therefore
the regexp matched, rather than nil or false. Of course I don't see
why he didn't just do

def vogon?
   VOGON_REGEXP =~ self
end

Or if he want to make sure vogon? would return either true or false:

def vogon?
   !!(VOGON_REGEXP =~ self)
end

or as you mentioned ===
Lou S. (Guest)
on 2006-05-06 06:03
(Received via mailing list)
To be safe, you might just want to subclass String instead:

    #!/usr/bin/env ruby

    class VWord < String
      V_VOC = %w{freddled gruntbuggly miturations} # ...
      V_RGX = Regexp.new( V_VOC.join('|'), ?i )

      def vogon?
        V_RGX === self
      end
    end

    word = VWord.new 'freddled'
    p word.vogon?

    word = VWord.new 'foobar'
    p word.vogon?

Sure.  It's annoying to have to call the constructor everywhere but at
least you don't mess up everyone who wants to use String.  I agree
with Robert that it's probably alright for short scripts, but then
again you know what happens when scripts decide to hang around longer
than you intended =)
Sam R. (Guest)
on 2006-05-06 23:28
(Received via mailing list)
Quoting removed_email_address@domain.invalid, on Sat, May 06, 2006 at 11:01:14AM
+0900:
> To be safe, you might just want to subclass String instead:
...
> Sure.  It's annoying to have to call the constructor everywhere but at

This pattern could help in some cases, but doesn't address a number of
common use cases in which you don't create the string. Like when the
string came out of a library you call, or you are implementing a library
and the string was an argument.

Cheers,
Sam
Lou S. (Guest)
on 2006-05-07 04:19
(Received via mailing list)
On 5/6/06, Sam R. <removed_email_address@domain.invalid> wrote:
> This pattern could help in some cases, but doesn't address a number of
> common use cases in which you don't create the string. Like when the
> string came out of a library you call,

Why can't you wrap the string after you get it?

  object = SomeLibraryClass.new( #... )
  wrapped = WrapperClass.new(object)

  # do your thing with wrapped

> or you are implementing a library and the string was an argument.

  module M
    class C
      def lib_function ( string_from_caller )
        wrapped = WrapperClass.new(string_from_caller)
        # again do your thing with wrapped
      end
    end
  end

The probem I see with all that is you risk what makes people--myself
included--hate Java:  you need to instanciate about 7 different
classes to write some output (or whatever).
Pistos C. (Guest)
on 2006-05-07 08:11
Logan C. wrote:
>> What exactly do you intend to achieve with ".respond_to? '>'"?
> or as you mentioned ===

Thanks for the tips about Regexp#===, guys.

However, I didn't mean for anyone to focus on the details of
String#vogon?, I only wanted to ask whether people considered it
acceptable practice to even create String#vogon?.

The other responses which gave "why don't you do X instead" look like a
parent giving into the whinings of a spoiled brat, and I thought the
philosophy of Ruby was that computers and software are our slaves, not
the other way around.

> This pattern could help in some cases, but doesn't address a number of
common use cases in which you don't create the string. Like when the
string came out of a library you call, or you are implementing a library
and the string was an argument.

This is indeed one of the problems with not extending String itself.

Are we phobic to change core classes because one file/app/library might
expect the method to behave one way, while another would expect
something else?

--- human.rb
class String
  def tasty_food?
    /pizza|sushi|curry|steak/ === self
  end
end
--- EOF
--- martian.rb
class String
  def tasty_food?
    /roaches|slime mold|pond scum|worms/ === self
  end
end
--- EOF
--- intergalactic-dinner-party.rb
require 'human'
require 'martian'
foods = load_food_array
foods.each do |food|
    serve food if food.tasty_food?
end
--- EOF

So far, I don't feel convinced that it's a bad idea to create
String#vogon?.

Pistos
Lou S. (Guest)
on 2006-05-07 09:21
(Received via mailing list)
On 5/7/06, Pistos C. <removed_email_address@domain.invalid> wrote:
> The other responses which gave "why don't you do X instead" look like a
> parent giving into the whinings of a spoiled brat, and I thought the
> philosophy of Ruby was that computers and software are our slaves, not
> the other way around.

Honestly, that's a little childish, don't you think?  You supposedly
wanted opinions on the matter, and you got exactly what you asked for.
 I'm not telling you how to do things, I'm simply offering
suggestions.

Computers and software are not slaves.  They are tools, and you need
to handle them with care.  I don't think that ability to reopen a
class should be taken away, but at the same time I do think you need
to be careful and *think* about what you are doing.  If you want to be
all gung-ho with the nail gun, that's fine, but you better know your
stuff.

> --- EOF
> foods = load_food_array
> foods.each do |food|
>     serve food if food.tasty_food?
> end
> --- EOF

Sure, that's bad enough, but that's not the real problem.  The BIG
problem is not that it will mess up your client code but that it has
the potential to muck things up in the library.  If you have more code
in human.rb that depends on the definition of tasty_food? it's all
going to blow up the minute you import martian.

> So far, I don't feel convinced that it's a bad idea to create
> String#vogon?.

If you've thought it through, and you're comfortable with the risks it
entails, then by all means go for it.  I just hope it doesn't come
back to bite you later.
Pistos C. (Guest)
on 2006-05-07 21:05
Lou S. wrote:
> Honestly, that's a little childish, don't you think?  You supposedly
> wanted opinions on the matter, and you got exactly what you asked for.
>  I'm not telling you how to do things, I'm simply offering
> suggestions.

If there are sensible restrictions that exist with good reason, I am
willing to "play inside the fenced area".  I just want to make sure the
fences have good reason to be there, to be the size and shape that they
are.

> Sure, that's bad enough, but that's not the real problem.  The BIG
> problem is not that it will mess up your client code but that it has
> the potential to muck things up in the library.  If you have more code
> in human.rb that depends on the definition of tasty_food? it's all
> going to blow up the minute you import martian.

Right, so is this the sole problem?  People coding app X assuming
CoreClass Y behaves a certain way, and then seeing that importing lib Z
causes borkage in X (due to a change of Y)?

Is it ever safe to assume that Y#is_x? is so obscure or domain-specific
that nobody would ever use it outside one's own application, and hence
it is okay to extend the core class with?

--- lemming-army-application.rb
class String
    def suitable_name_for_lemming_drill_sergeant?
        # ...
    end
end
--- EOF

>> So far, I don't feel convinced that it's a bad idea to create
>> String#vogon?.
> If you've thought it through, and you're comfortable with the risks it
> entails, then by all means go for it.  I just hope it doesn't come
> back to bite you later.

I'm in the process of thinking it through, hoping to get some thoughts
from others.  I don't know for sure yet either way, whether it's good or
bad.  If it's bad, I want to stop doing it.  If it's good, then I can
feel less trepidation whenever I do do it.

Pistos
Lou S. (Guest)
on 2006-05-07 21:56
(Received via mailing list)
On 5/7/06, Pistos C. <removed_email_address@domain.invalid> wrote:
> Right, so is this the sole problem?  People coding app X assuming
> CoreClass Y behaves a certain way, and then seeing that importing lib Z
> causes borkage in X (due to a change of Y)?

Yes, I think that's probably the biggest problem.  The other main
issue is the one you pointed out in the human/martian example.  I
believe that messing up the library is a worse situation, because the
library isn't in as good of a position as your code in terms of
knowing when things have been altered.  In your client code, you are
explicitly doing a require, so hypothetically you are aware of the
consequences of importing the second library.

> Is it ever safe to assume that Y#is_x? is so obscure or domain-specific
> that nobody would ever use it outside one's own application, and hence
> it is okay to extend the core class with?

I agree with you that you would probably be safe in 99.8% of cases,
but sometimes you are better off safe than sorry.  Ruby is flexible
enough to let you go either way, and that's a good thing.  If you are
confident go for it and modify the class; if not code more
defensively.

> I'm in the process of thinking it through, hoping to get some thoughts
> from others.  I don't know for sure yet either way, whether it's good or
> bad.  If it's bad, I want to stop doing it.  If it's good, then I can
> feel less trepidation whenever I do do it.

When it comes down to it, you probably need to evaluate the decision
on a case by case basis.  On a short script I might go ahead and open
String back up, but in a larger application, I'd tend to be more
careful.

Perhaps another thing to consider is on a more logical level.  If the
method is not something that *any string* should be able to do, you've
probably put it into the wrong class.

Good luck either way you decide, and write lots of tests =)
Pistos C. (Guest)
on 2006-05-07 23:16
Lou S. wrote:
> I agree with you that you would probably be safe in 99.8% of cases,
> but sometimes you are better off safe than sorry.  Ruby is flexible
> enough to let you go either way, and that's a good thing.  If you are
> confident go for it and modify the class; if not code more
> defensively.

Hm, okay.  I think I'll keep living a bit dangerously in this regard,
then.  :)  I would tread much more carefully, though, if I were naming
things that people might possibly use or assume in other places.

> Perhaps another thing to consider is on a more logical level.  If the
> method is not something that *any string* should be able to do, you've
> probably put it into the wrong class.

Well, if I'm interested if a string is #vogon? or not, I can't see why I
would try to segregate strings into two groups.

The only other alternative that comes to mind is writing un-OO stuff
like:

class SomeApplication
  def is_vogon?( str )
    VOGON_REGEXP === str
  end
end

Which just feels unRubylike to me.

> Good luck either way you decide, and write lots of tests =)

Thanks.  Your input has been helpful, and I appreciate you taking the
time to share it.

Pistos
Lou S. (Guest)
on 2006-05-07 23:51
(Received via mailing list)
On 5/7/06, Pistos C. <removed_email_address@domain.invalid> wrote:
> > Perhaps another thing to consider is on a more logical level.  If the
> > method is not something that *any string* should be able to do, you've
> > probably put it into the wrong class.
>
> Well, if I'm interested if a string is #vogon? or not, I can't see why I
> would try to segregate strings into two groups.

I agree.  I think it is a reasonable expectation for a string to tell
you if it is_vogon?  Let's say you wanted to inflect a word based on
vogon grammar rules.  That would be a method that only vogon words
should respond to, so at that point I think you'd want a subclass.

It all depends on your intentions and on your design decisions.
anonymous kibbitzer (Guest)
on 2006-05-10 20:10
Pistos C. wrote:
> Lou S. wrote:
>> Perhaps another thing to consider is on a more logical level.  If the
>> method is not something that *any string* should be able to do, you've
>> probably put it into the wrong class.
>
> Well, if I'm interested if a string is #vogon? or not, I can't see why I
> would try to segregate strings into two groups.
>
> The only other alternative that comes to mind is writing un-OO stuff
> like:
>
> class SomeApplication
>   def is_vogon?( str )
>     VOGON_REGEXP === str
>   end
> end
>
> Which just feels unRubylike to me.

Just 2 cents here...

I think what Lou was suggesting is that normally, something like a Vogon
module might be a better place for the is_vogon? method...

module Vogon
  def is_vogon?( str )
    VOGON_REGEXP === str
  end

  module_function :is_vogon?
end
.
.
.
Vogon.is_vogon?( "bleem" )
This topic is locked and can not be replied to.