

I'm Sick of This - bdfh42
http://github.com/raganwald/homoiconic/blob/master/2009-04-08/sick.md#readme

======
jrockway
Interesting. We have sort of solved this problem in Perl. When you want to add
a method to a class, you do it by applying a role. If two roles conflict, like
by adding a method with the same name, you have to resolve the conflict, or
the class won't compile. (The class can rename methods, but you can also call
the method with a name that will always work; $instance->Role::Name::method.)

We can also apply roles at runtime. This means you can add a method to a
single _instance_ of a class. So Rails could add its version of sum to its own
instances of Array, and Classifier could do the same. Then there would be no
conflict, except when Classifier tries to add sum to the Rails array... but at
least you get a fatal error message instead of unexpectedly wrong behavior.

We can do better, though. The way around this problem is to implement (and
CPAN) a role that adds a sum method to arrays, and to have both Rails and
Classifier use that role. Then they can both share arrays, and when one tries
to apply the sum role, the already-applied sum role will be used instead.

They both get their sugar, the sugar is isolated to the smallest area possible
(a single instance), and they can both confidently share those arrays-that-
can-sum.

And oh yeah, with Moose::Autobox, you can apply roles to Perl's native types
(arrays, hashes, etc.), and call methods on them. So this is not "pie in the
sky", it's an already-solved problem for us :)

Edit: ContextL also provides an interesting solution, "layers". Each module
can define their own layer, add their own sum method to it, and activate it
inside themselves:

    
    
        (defclass array ...)
    
        (deflayer rails-sum ...)
        (define-layered-method sum :layer rails-sum ((self array)) ...)
    
        (deflayer classifier-sum ...)
        (define-layered-method sum :layer classifier-sum ((self array)) ...)
    
    
        (in-package :rails)
        (defun count-users (users-array)
            (with-active-layers (rails-sum)
                (sum users-array))) ; uses rails' sum method
    
        (in-package :classifier)
        (defun count-results (results-array)
            (with-active-layers (classifier-sum)
                (sum results-array))) ; users classifier's sum method
    
        (in-package :my-app)
        (defun broken-code (array)
            (with-active-layers (rails-sum classifier-sum)
                 ;; ERROR, methods conflict
             ))
    

This is a pretty good approach, but not as good as agreeing on semantics in
advance.

~~~
raganwald
_We can also apply roles at runtime. This means you can add a method to a
single instance of a class. So Rails could add its version of sum to its own
instances of Array, and Classifier could do the same._

This is possible with Ruby, but culturally rare. Quite possibly because
(blamestorming mode=ON) Rails simply patches whatever it wants, whenever it
wants and many people follow its lead. I personally avoid the practice because
it is difficult to contain in Ruby. For example, if I am handed an Array it is
easy to extend that instance with its own #sum method, and when I'm done with
the array I can remove my own extension.

However, what happens if, while I am still holding this extended array, I pass
the array to some other method that wants to extend it with a #sum method?
Same problem. Working on an instance method does reduce the scope of each
modification and thus make collisions statistically less likely, so I am all
for it. But at the same time, when I step back and look at the big picture, I
yearn for something that solves the problem in a more fundamental way.

~~~
jrockway
It is interesting... Perl has such a reputation for being unmaintainable that
Perl programmers today are very careful to spend extra effort making their
code as maintainable as possible.

I think a lot of practicing Ruby (and Python) programmers are under the
impression that Ruby magically solved Perl's maintainability shortcomings, and
so they don't invest any effort into writing maintainable code. The result is
... unmaintainable code :) It turns out that programmers write the
unmaintainable code, not programming languages.

~~~
raganwald
I feel that using patterns or coding conventions to make Ruby metaprogramming
maintainable is isomorphic to greenspunning higher-level programming practices
with if statements and gotos. It is assuredly what a good programmer does to
get the job done with the tool that is provided. But a toolsmith thinks about
ways to improve the tool itself.

------
raganwald
Off topic: Thank-you HN for having a "Don't editorialize the article's
headline" policy. Over on proggit this was submitted with the title
_Metaprogramming has its pluses and minuses._ I feel that statement completely
misses the point of the post.

[http://www.reddit.com/r/programming/comments/8ayr1/metaprogr...](http://www.reddit.com/r/programming/comments/8ayr1/metaprogramming_has_its_pluses_and_minuses/c08qdd8)

~~~
soundsop
Usually, I would agree, except in this case the title gives absolutely no
indication as to the topic of the article. Putting the word metaprogramming
somewhere in the title would have been better.

~~~
raganwald
Well, I could say "I'm sick of this metaproggraming shit." But I'm not sick of
the metaprogramming, I'm sick of the conflicts. Or there's "I'm sick of the
conflicts between other people's metaproggraming shit." That's closer to it,
but doesn't capture my disappointment in the lack of progress in writing
better tools. So there's "I'm sick of the lack of really cool metaproggraming
shit" which is actually what bothers me and has the informal profanity I was
going for.

So... Please feel free to fork my repo and retitle it _I'm sick of the lack of
really cool metaprogramming shit_ with my blessing.

~~~
tvon
The blog post title is fine, it's the post on HN that could use a little
context. I think that's the point of this thread, anyway.

------
tdavis
It disgusts me on levels I cannot describe that this is standard operating
procedure for ruby modules (gems, etc) and is the main reason I could never
use it. Adding and replacing methods on builtin classes? Are these people out
of their minds?! I don't understand it. I've used monkey patches once or twice
and it always feels really dirty.

~~~
blasdel
Ruby, despite having 4? sigils as scoping operators, pervasively screws up or
simply doesn't have support for scoping.

It really should have support for having class modifications scoped to your
module (and accessible manually from outside), but I couldn't find it.

If the language is such that everything "should" be a method on the broadest-
possible class, people are going to monkeypatch, because it feels "wrong"
otherwise. People just want to write idiomatic code.

In Objective-C this ends up not being an issue, because there's very little
usage of non-Apple libraries -- plus the default way of adding methods doesn't
allow replacement, and Apple libraries are well-versioned (if you build your
app for >10.4, API changes in 10.5 are invisible).

~~~
akkartik
_why's done some work on this: <http://github.com/why/potion/tree/master>

    
    
         Scoped mixins. Basically, you can modify
         object behavior within a scope. So changes
         to core classes can be localized.
         ++ INCOMPLETE ++

------
calambrac
I love how Scala solves this problem (or rather, provides the same
functionality while avoiding the problem altogether):

[http://louisbotterill.blogspot.com/2009/02/scala-implicit-
co...](http://louisbotterill.blogspot.com/2009/02/scala-implicit-
conversions.html)

[http://www.codecommit.com/blog/scala/scala-for-java-
refugees...](http://www.codecommit.com/blog/scala/scala-for-java-refugees-
part-6)

Edit - more links, more detail:

[http://technically.us/code/x/the-awesomeness-of-scala-is-
imp...](http://technically.us/code/x/the-awesomeness-of-scala-is-implicit)

<http://scala.sygneca.com/code/simplifying-jdbc>

------
andreyf
Alan Kay wrote a paper about a very elegant metaphor to solve this, it's
called "Worlds":

    
    
        http://www.vpri.org/pdf/rn2008001_worlds.pdf
    

You create a "world" for your Classifier gem, which (in a trivial
implementation) makes local copies of any global objects when they're modified
instead of actually changing them. ActiveSupport runs in yet another "world",
and knows nothing about the "local" changes to the global variables Classifier
made.

~~~
jrockway
I don't think this is quite what Worlds are. Worlds would not allow you to
have a data structure that is used by both Rails and Classifier.

FWIW, ContextL is closer to the solution:

<http://common-lisp.net/project/closer/contextl.html>

But it's still better to just add a standard "array that does 'sum'" to the
language, or for Rails and Classifier to cooperate.

~~~
stcredzero
Cooperation is dandy if n = 2, but for n > 2, this can get cumbersome.
Unfortunately method names like 'sum' are very attractive.

A language/library designer shouldn't have to think of "all of the short or
otherwise possibly overloaded nouns and verbs people might like to use, ever."
Being able to say you are going to do a 'sum' which means X in this particular
context is very powerful.

This notion should also be unified with version control. (As in OS X, where a
process only sees the versions of libraries that it is supposed to run
against.)

~~~
jrockway
_A language/library designer shouldn't have to think of "all of the short or
otherwise possibly overloaded nouns and verbs people might like to use, ever."
Being able to say you are going to do a 'sum' which means X in this particular
context is very powerful._

In this specific case, what's needed is more abstraction. Consider the
approach Haskell takes:

[http://www.haskell.org/ghc/docs/latest/html/libraries/base/D...](http://www.haskell.org/ghc/docs/latest/html/libraries/base/Data-
Monoid.html)

------
mpk
What, you mean this?

~ $ irb

irb(main):001:0> Object.methods.size

=> 90

irb(main):002:0> require 'activesupport'

=> true

irb(main):003:0> Object.methods.size

=> 177

[ugh, no preview..]

------
misuba
Uh... aren't we kind of burying the lede here?

Ruby has macros now.

For some limited definition of 'macros,' maybe, but screw it, I'm there.

Thank you, Mr. Braithwaite!

~~~
andreyf
I've heard this in person and now here, but I can't find info on it - care to
link me to these macros you speak of?

~~~
davidmathers
<http://github.com/raganwald/rewrite/tree/master>

------
johnrob
JSON.js does something similar. It puts methods in the javascript object
prototype for generating and parsing json strings. WTF? Is it so inconvenient
to just expose two functions to do this?

This was a problem because it broke some jquery functionality, and I ended up
removing the portion of JSON.js that was updating the object prototype.

~~~
nostrademons
It was a big problem in the JavaScript world, until JQuery and YUI and Google
came out and said "Thou shalt not modify Object.prototype" and started leading
by example. But Prototype and Mootools and a bunch of smaller libraries still
modify the prototypes of built-in objects, which is the main reason I won't go
near those libraries. Unfortunate, since they're otherwise pretty good...

I think new versions of JSON.js (json2.js) don't modify Object.prototype
anymore - Crockford has certainly learned his lesson.

------
jcapote
I'm curious as to how lisp has dealt with this for 40+ years

~~~
dunham
I'm not a lisp expert, but my understanding is that CLOS uses packages for
this.

In CLOS, methods (generic functions) live outside classes. So, like normal
functions, their names are qualified by the packages that they are defined in.
If you want the 'sum' method declared in package 'foo', you either import it
into the package/namespace you're working in, or you fully qualify the name
('foo::sum', I think).

This lets you extend built-in classes within your package without getting in
anyone else's way.

~~~
gruseom
Great explanation. It doesn't really have to do with CLOS though. The package
system works on any names whether you use classes or not (we don't).

One little detail I love because it illustrates Lisp's design style nicely. If
you're using an exported (public) name, you say foo:sum. But if the name is
private and you want to breach encapsulation to get at it, you say foo::sum.
The language makes you state your intention, but makes it easy to do so and
doesn't get in your way.

------
eatenbyagrue
A lot of this is solved by namespaces in Ruby (Modules.) Unfortunately,
developers don't really apply them consistently. A best practice should be to
put everything in it's own namespace. Been playing around with .NET MVC and
C#, and this is something it manages really well.

~~~
andreyf
How exactly are conflicting changes to global variables soved by namespaces?

~~~
tjstankus
I don't think global variables were mentioned in the article. Do you mean the
global (top-level) namespace? If so, then yeah, "Don't do that" is pretty much
the solution.

~~~
andreyf
Sorry, maybe this isn't correct Ruby nomenclature, but I see Array simply as a
global object, which can be modified, with rather obvious consequences. It's
sad that I feel old enough to say this, but isn't this a problem that we
solved a long time ago?

"Don't do that" is one solution, Alan Kay's idea of "worlds" is another.

------
Locke
I'd almost blame this problem on Ruby not having its own standard Array#sum
method (or more likely it should be a method on Enumerable?). I'm guessing a
lot of projects add their own, so it's a natural source of conflict.

Secondly, it seems like most conflicts like this occur between Rails and some
other library. Rails, via ActiveSupport, adds a lot of extensions to core
Ruby. I kind of wish it didn't. Framework or general purpose library
developers should recognize that their library will likely be combined with
many others and avoid conflicts by sticking to what's available in the
standard, namespacing their code, etc.

I like Ruby, but I'll readily admit it's not a perfect language. I'm sure
someday a better Ruby-like language will come along. Hopefully, it'll be a
language with better support for avoiding these kind of conflicts without
removing support for open classes. I'm sure it's an interesting technical
challenge.

~~~
raganwald
While I grouse about Rails, here is the challenge I posed last July at
Rubyfringe: Forget about Rails providing all these extensions for Rails
programmers to use. How does Rails perform this kind of metaprogramming for
its own use? How can its own internal libraries use Array#sum while leaving
Array unmodified for Rails programmers?

~~~
Locke
In my own library, I use inject instead of adding Array#sum. But, I know
that's sidestepping the issue, not solving it.

Honestly, I'm not sure _how_ Rails uses #sum. I'm not sure whether they
_really_ need it or not. I suspect not.

I'm not a big fan of a lot of non-standard additions to Ruby. For example, all
the #try, or #returning, or even your #andand. I don't consider any of those
to have enough intrinsic value to be worth the risk of collisions with someone
else's subtly different implementation.

~~~
raganwald
_I'm not a big fan of a lot of non-standard additions to Ruby. For example,
all the #try, or #returning, or even your #andand. I don't consider any of
those to have enough intrinsic value to be worth the risk of collisions with
someone else's subtly different implementation._

#andand is soooooooo "raganwald." The Homoiconic Way is to use rewrite: #try,
#returning, and #andand are all removed from your code and never conflict with
other people's implementations ;-)

------
herdrick
What is naked recursion?

~~~
raganwald
Functions/methods that call themselves, as opposed to having a higher-level
function that takes a function as an argument and does something recursive
with it.

[http://github.com/raganwald/homoiconic/blob/master/2008-11-2...](http://github.com/raganwald/homoiconic/blob/master/2008-11-23/recursive_combinators.md#readme)

~~~
herdrick
Ah, so naked recursion is recursion.

EDIT: I was going to criticize your idea that there is anything lacking in
"naked recursion". It's a really powerful technique and in fact I just wrote a
recursive function for production code an hour ago. Comment continued here:
<http://news.ycombinator.com/item?id=555714>

