Monkeypatching is Destroying Ruby

T

Trans

I'd be at least a little interested in potentially offering developers
the chance to 'lock' their classes from monkey patches. This could be
useful to the 'core' library that comes with Ruby, and to at least make
developers look at extension points provided via an actual API instead
of just immediately jumping on monkey patching for solving all problems.

That's ridiculous.

Look. What is the problem? That I might extend a class and you might
extend a class with the same method and thus we can't use each others
code in the same program? Forget overriding core/standard methods --
anyone who does that knows they must do so with SUPER EXTREME caution.
But if someone is writing and end-user application, it doesn't really
matter one way of the other --the code is not intended to be shared.
Extend to your hearts desire. One only needs to be aware of potential
conflicts with 3rd party libs they might use. And for those, in which
the developer is intending for their code to be reusable, then the
developer needs to tread more carefully and follow some simple rules.
Only extend core and standard classes when it's a very clear and
general need. In which case it's a good idea to checkout projects that
attempt to provide some general standardization around such methods
(eg. Facets). It's likely your method is already available, and your
lib's users can have a reasonable basis for knowing what to expect.
Beyond that, if you still really want to extend a core/standard class
in a way vunique to your particular program then give it an equally
unique name --generally putting you project's name as the first part
of the method. For example, the YAML lib adds methods like
#yaml_properties.

By following these rules, getting along with other programs is a
pretty safe bet. And we don't need to add restrictions that will just
make life harder when we do have good reasons to MP.

I should point out one other rule of thumb often overlooked as a
consequence of MP: DO NOT use #respond_to? as a means of determining
if an object is extended by a module or is an instance of some class/
superclass. That's much more likely to lead to unexpected issues with
MPs.

T.
 
J

Joel VanderWerf

Jari said:
IMO, this is not an either/or case. Why not just add to_csv() only to
the instance who actually could need it?

How far in advance do you know that an instance needs this method?

If you wait too long, you end up with another case statement (again, on
the class of the object, yuck) to decide which #to_csv is appropriate
for the object.

If you add #to_csv eagerly, then the requirement that this object
respond to #to_csv becomes a strong coupling between the point of
creation and the #i_need_csv implementation.
 
J

James Britt

Trans said:
That's ridiculous.

Fears about open classes sound very much like what people say about
dynamic typing.

"#{@core_feature} is unpredictable!"

"#{@core_feature} creates problems that cannot be found until run-time!"

"#{@core_feature} is unsafe!"

"#{@core_feature} should be locked down!"

It's not that these claims are entirely untrue, it's just that, in real
life, most people simply do not encounter the alleged problems.

Code that is poorly written or does not play well with others tends to
get discarded.



--
James Britt

"Trying to port the desktop metaphor to the Web is like working
on how to fuel your car with hay because that is what horses eat."
- Dare Obasanjo
 
E

Eric Mahurin

[Note: parts of this message were removed to make it a legal post.]

The big advantage is that, when writing a method that needs to receive
CSV data as an argument, you don't have to do type checking on the
argument - any object that responds correctly to the #to_csv message
can be used.

def i_need_csv( obj )
data = obj.to_csv
do_something_with( data )
end

This gives more freedom to the user of the library who might have an
object of their own class which knows how to turn itself into CSV
data. Your suggestion would lead to something like:

def i_need_csv( obj )
data = case obj
when Array
CSV::from_a( obj )
when Hash
CSV::from_hash( obj )
else
raise "Sorry, I'm not duck-type friendly!"
end
end

Yuck.


Point taken. Monkey patching might be the best solution in this situation.
Assuming you want to be able to convert any arbitrary object to a CSV, this
might be best.

Another possibility would be something like this:

module CSV
From = {
Array => proc {|a| ...}
Hash => proc {|h| ...}
...
}
...

Then use CSV::From[obj]. The problem is that if you build a class later
that wants to work with CSV, you'd need to modify this CSV::From "global".
This "global" modification isn't much better than monkey patching "global"
modification.

I think John does illustrate why monkey patching can be useful with
duck-typing. In statically-typed languages (like C++), the above can be
solved by overloading a global function based on type (type should prevent
collisions). Don't take this to mean I want static-typing. I'm the biggest
fan duck-typing.

Still, we should not use monkey patching as our first tool. We should think
of it just like global variable modification, IMO.

Eric
 
A

Avdi Grimm

It's not that these claims are entirely untrue, it's just that, in real
life, most people simply do not encounter the alleged problems.

Code that is poorly written or does not play well with others tends to
get discarded.

Except that I'm working in the real world, and I run into these
problems practically every day. Read through some Rails plugin code
sometime - nearly every significant, popular Rails plugin does what it
does by re-opening classes. Probably because this is the coding style
that Rails demonstrates and encourages. Now we can argue about this
being a Rails problem rather than a Ruby one, but again, that's where
people are learning the language these days.

If you have the luxury of working on small projects with few
developers and the time to develop everything in-house rather than
relying on third-party gems and plugins, that's great. But I and the
people I know *are* encountering these problems. And in the majority
of cases, they are *completely* *avoidable*. And there is usually
nothing about the problem that makes a monkey-patch a desirable or
even an easier way to implement the feature - it's simply done that
way because that's how everyone else is doing it.
 
J

Jari Williamsson

Joel said:
How far in advance do you know that an instance needs this method?

If you wait too long, you end up with another case statement (again, on
the class of the object, yuck) to decide which #to_csv is appropriate
for the object.

If you add #to_csv eagerly, then the requirement that this object
respond to #to_csv becomes a strong coupling between the point of
creation and the #i_need_csv implementation.

I don't think it would matter in this case. You should be able to create
a dynamic implementation without case statements, since a CSV
implementation would be dependent on the #to_a method? (And #to_a for an
array object just returns self.)


Best regards,

Jari Williamsson
 
M

MenTaLguY

I'd be at least a little interested in potentially offering developers
the chance to 'lock' their classes from monkey patches.

You can do this today if you want; all you need to do is freeze the class.

class Foo
# ... your definitions
end

Foo.freeze # Foo is no longer an open class

-mental
 
C

Clifford Heath

Pit said:
Could you describe "the general thing" a little bit more? As I
understood it, he wants to limit a "monkey patch" to the scope of a
Module. Which is what I've done. I've only used the import-module
library and added a more user-friendly syntax. I'm not inspecting
caller at all, and import-module doesn't either. What am I missing?

Well, I can't find import-module-extended, but assuming it's like
import-module... you're missing:

* performance
* isolation of instance variables that the extensions might create
* ability for an extended class to *always* appear extended when
called from anywhere in a class that knows about those extensions,
without having to constantly remember to add the extensions on each
call.

Is that enough to claim "no sensible way" of doing it?
 
F

furtive.clown

[...]
My point rather is this should somehow be standardized, i.e. the standard
library should provide standard means to do this.

This is what I was attempting to say in my previous post on this
thread. You mentioned defadvice, which is a good example of a "long
reach" mechanism employed by top-level code in order to tweak lower-
level code. The reason this thread exists is because there is no
centralized mechanism in ruby to help programmers from stepping on
each others' toes. Making ad hoc modifications to base-level classes
is not a scalable practice.

So I will ask my question again: Is (or was) there a serious plan for
something like selector namespaces in ruby 2.0? I found
http://rubygarden.org/ruby/page/show/Rite
which appears to be down; google cache:
http://64.233.169.104/search?q=cach.../ruby/page/show/Rite&hl=en&ct=clnk&cd=1&gl=us

Responses such as "What is the problem?" come from those who have been
lucky enough to evade these issues in their own work. But eventually
one will wish to use some code written by someone else which
introduces conflicts. It's only a matter of time, unless you
personally write every line of code in your project. So the answer to
"What is the problem?" is that the technique doesn't scale.

--FC
 
J

Joel VanderWerf

Robert said:
Hmm I do not think that one can overrule freeze; I never found a way,
anyone else?
Cheers
Robert

You can monkeypatch Class#freeze, as long as you get there first ;)

class Class
def freeze; end
end

class Foo
def bar; end
end

Foo.freeze

class Foo
def zap; end
end
 
T

Trans

[...]
My point rather is this should somehow be standardized, i.e. the standard
library should provide standard means to do this.

This is what I was attempting to say in my previous post on this
thread. You mentioned defadvice, which is a good example of a "long
reach" mechanism employed by top-level code in order to tweak lower-
level code. The reason this thread exists is because there is no
centralized mechanism in ruby to help programmers from stepping on
each others' toes. Making ad hoc modifications to base-level classes
is not a scalable practice.

So I will ask my question again: Is (or was) there a serious plan for
something like selector namespaces in ruby 2.0? I foundhttp://rubygarden.org/ruby/page/show/Rite
which appears to be down; google cache:http://64.233.169.104/search?q=cache:ej4aPcNY41QJ:rubygarden.org/ruby...

Solutions to this tend to be pretty weighty. Ruby is already a rather
slow language. A while back I suggested this idea:

module MyModule
class String < ::String
def something; "something"; end
end

def self.tryme
"string".something
end
end

def tryme2
"string".something
end

MyModule.tryme #=> "sometging"
tryme2 #=> NoMethodError

Austin thought it was the worst idea in the world. He may be right,
I'm not sure. In either case it would add another level of
consideration to the language.
Responses such as "What is the problem?" come from those who have been
lucky enough to evade these issues in their own work. But eventually
one will wish to use some code written by someone else which
introduces conflicts. It's only a matter of time, unless you
personally write every line of code in your project. So the answer to
"What is the problem?" is that the technique doesn't scale.

What technique is perfectly scalable? There is always the potential of
name clash no matter what technique is used. It's an unfortunate fact
of life, but if the library you are trying to use is poorly written --
well, then life sucks. Fix the library or find another.

I think you may be looking foe a magic bullet that doesn't exist.
Also, do you have examples of this issue?

T.
 
E

Eivind Eklund

Code that is poorly written or does not play well with others tends to
get discarded.

We still have Gems, which don't play well at all with others (on a
technical basis) - witness the conflicts with the Debian team, and
what every other Unix package maintainer says about this. This proves
that this mechanism doesn't work so well if there is a size/momentum
advantage.

Also, discussing what is appropriate and not is a good way to educate
people, and to help us all learn to think more clearly in the area.

Eivind.
 
J

James Gray

I think it's the ideal tool for adding compatibility methods.

Along these lines, I read a chapter in Design Patterns in Ruby last
night that suggests using Ruby's open classes as one possible way to
implement the Adapter pattern. The book includes a pretty good
discussion of the plusses and minus to this approach and makes
recommendations about when it's probably worth the trade-off. It's a
good read.

James Edward Gray II
 
J

Jones, Brian - McClatchy Interactive

It's not that these claims are entirely untrue, it's just that, in=20
real life, most people simply do not encounter the =20 alleged problems.

Code that is poorly written or does not play well with=20 others tends=20
to get discarded.
=20
Except that I'm working in the real world, and I run into=20
these problems practically every day. =20[/QUOTE]

It may be enough to simply have ruby warn us when and where a class has
been re-opened and a means to ignore it by file/path or namespace if we
want.

Brian
 
E

Eric Mahurin

[Note: parts of this message were removed to make it a legal post.]

Along these lines, I read a chapter in Design Patterns in Ruby last
night that suggests using Ruby's open classes as one possible way to
implement the Adapter pattern. The book includes a pretty good
discussion of the plusses and minus to this approach and makes
recommendations about when it's probably worth the trade-off. It's a
good read.

James Edward Gray II


I don't have the book. I wouldn't mind seeing the +/- list.

You are talking about this, right?

http://en.wikipedia.org/wiki/Adapter_pattern

Using what this link above suggests (adapter "has-a" or "is-a" adaptee) is
the better way to go, IMO. There are multiple ways to do this in Ruby.
Monkey-patching/open-classes makes the solution: adapter IS adaptee. I
don't know if you can even call it the adapter pattern since you are
destroying the adaptee interface.
 
J

James Gray

I don't have the book. I wouldn't mind seeing the +/- list.

I don't think I would do it much justice condensing it down for this
email, but it basically amounted to how well you know the code you are
adapting and how simple the changes are. The author felt it was
probably OK if you knew the class involved well and you were just
aliasing a method or two. Again though, I'm grossly simplifying here.

The book also talked about adding the methods needed to the singleton
class of individual objects. I thought that was an awesome twist that
we probably don't consider enough.
You are talking about this, right?

http://en.wikipedia.org/wiki/Adapter_pattern
Yes.

I don't know if you can even call it the adapter pattern since you are
destroying the adaptee interface.

I view it as a Ruby way to think about the problem. Don't they always
say that intent is the most important aspect to the design patterns?

James Edward Gray II
 
T

Trans

We still have Gems, which don't play well at all with others (on a
technical basis) - witness the conflicts with the Debian team, and
what every other Unix package maintainer says about this. This proves
that this mechanism doesn't work so well if there is a size/momentum
advantage.

I would now suggest that it is the FHS that is holding us back, not
the other way around --talk about your size/momentum advantage.

Suggested reading:

http://gobolinux.org/index.php?page=doc/articles/clueless
Also, discussing what is appropriate and not is a good way to educate
people, and to help us all learn to think more clearly in the area.

Very true.

T.
 
P

Pit Capitain

2008/2/26 said:
Well, I can't find import-module-extended, but assuming it's like
import-module... you're missing:

Clifford, thanks for listing your requirements.
* performance

Comparable to other AOP frameworks in Ruby. That might be an obstacle
for you, but it's not for me yet.
* isolation of instance variables that the extensions might create

If you are really serious about this, there are ways to achieve this goal.
* ability for an extended class to *always* appear extended when
called from anywhere in a class that knows about those extensions,
without having to constantly remember to add the extensions on each
call.

Please look at my sample code again. It does exactly what you describe.
Is that enough to claim "no sensible way" of doing it?

Not for me, as I said above. But since there's no formal definition of
"sensible ways", there might be no sensible way for you. Maybe we
should take this to another thread or to private mail if you want to
discuss it further.

Regards,
Pit
 
R

Rick DeNatale

I would now suggest that it is the FHS that is holding us back, not
the other way around --talk about your size/momentum advantage.

Suggested reading:

http://gobolinux.org/index.php?page=doc/articles/clueless

In the case of Debian and Gems, I think it's a mixture of a somewhat
draconian interpretation of the FHS, and a desire to make the gems
themselves somehow work like debian packages, which don't envision
things like having multiple versions of a gem installed concurrently.
 

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,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top