Extending Code Cleanly

(Molitor, Stephen L) #1

Bigdecimal does this too. There's some nifty extensions to standard
numeric classes, but you explicit required those features.

I'm not sure if framework vs. library distinction holds up. Most of
Rails is structured as libraries that are usable outside of Rails
anyway. (Not sure about the date / fixnum stuff, but I would assume
so.) I think it would be ok, however, if the standard requires for
Rails (or whatever) included the extensions, but there were other
require files available that didn't:

require 'foo'
# or:
require 'foo_without_extensions'

Another question is what if you wanted to extend standard classes for
use in the internal implementation of your library. As it stands now,
that's probably not a good idea. But with the proposed Ruby 2 namespace
feature it could be safe -- it the extension would only apply within
your library.

So how about this:

1. Don't change the behavior of existing stuff.
2. Adding new methods is OK, but document clearly and make those
extensions optional.

Steve

···

-----Original Message-----
From: Austin Ziegler [mailto:halostatue@gmail.com]
Sent: Wednesday, August 24, 2005 6:40 PM
To: ruby-talk ML
Subject: Re: Extending Code Cleanly

On 8/24/05, David Brady <ruby_talk@shinybit.com> wrote:

Austin Ziegler wrote:
>Sometimes, as in #to_foo, it may be appropriate, but that should
>*probably* be done with:
>
> require 'foo'
> require 'foo/string'
>
>
I *REALLY* like this idiom!

require 'mymodule/standardmodule' # allow mymodule to make injections
to standardmodule.

Nifty!

Thanks. I didn't invent it, but it seems the *best* way to solve what
I'd said re: extending cleanly.

If Rails wanted to do this (as a library, not a framework), then it
might be require 'activerecord/date/fixnum' or something like that.
But that's just what *I* would do, and not what everyone would do.

-austin
--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

(David Brady) #2

Molitor, Stephen L wrote:

require 'foo'
# or:
require 'foo_without_extensions'

I would also be happy with 2 ways to load foo:

require 'foo'
require 'foo/string'
require 'foo/fixnum'
require 'foo/date'

being equivalent to

require 'foo/extensions'

or somesuch. The contents of 'foo/extensions' would literally be just the four earlier requires.

Both ways (foo,foo_without_extensions vs. foo,foo/extensions), have pros and cons. I guess most people would want extensions rather than to disable them, and in general I like to see the shorter syntax do the most desirable thing; so maybe 'require "foo"' should bring in extensions, and foo_without_extensions should be the nonintrusive version.

Then again, when your core classes Go Wrong, it's pretty horrible, so maybe pulling in extensions explicitly is better.

The 'foo/extensions' thing looks the most "honest" to me--you're telling the reader that you are mucking in code space that they might not expect. I see only two issues with the syntax:

- should 'foo/extensions' ALSO import 'foo'? I think it should. My C++ tainted mind wanted to say no at first, but I think this is not the Ruby way. require 'test/unit' brings in Test AND Test::Unit just fine, for example.

- because you can put extensions anywhere, you cannot trust in the "reverse case", e.g. just because 'foo/extensions' exists, that require 'foo' *won't* go making extensions. I would be content to see this in an RCR: a warning level could exist that will tell you if a file named other than 'extensions.rb' reopens an existing class, and this warning should be disabled by default (let's not hamper the agility of short scripts). A safe_level could also exist that turns that warning into an error, perhaps unless $0 == __FILE__ (again to favor the short scripts).

Finally, and this is left as an exercise to those readers who know a lot more Ruby than me, I think it is possible to write a ruby module that provides this functionality right now. Adding an extension to Kernel#extend and related functions to raise an exception if a Core or StdLib method is overriden in a file not named extensions.rb. Pity you can't freeze methods, or you could prevent people hacking out your change by freezing Kernel#extend (but still leaving Kernel open to extension). But now we're talking about deliberate, premeditated evil, and there's just no way to prevent that in the code--that's what baseball bats and poorly lit parking lots are for. :slight_smile:

-dB

···

--
David Brady
ruby-talk@shinybit.com
I'm having a really surreal day... OR AM I?