

Refining Ruby - jballanc
http://blog.headius.com/2012/11/refining-ruby.html

======
kaiuhl
The most convincing argument as to why not to include refinements in the Ruby
spec comes from alternative implementations like JRuby and Rubinius. Method
dispatch will need to be vastly more complex (and have scope-limited caching)
to support this feature. Much, much slower. Folks using these implementations
are still on the fringe, but it seems wrong to add a feature to Ruby that
makes both more complicated code and slower running code.

Wasn't Object#source_location added in 1.9 to help folks understand and find
method definitions in code? Shouldn't we simply test our code to ensure it
functions correctly and not force additional semantics all the time to fix a
rare issue?

~~~
riffraff
> Shouldn't we simply test our code to ensure it functions correctly and not
> force additional semantics all the time to fix a rare issue?

The problem is you can't. If library A and B define String#to_param
differently for internal use you can't use them together, while both of them
are perfectly correct on their own.

~~~
kaiuhl
I've been writing Ruby professionally for six years and this has never been an
issue for me or folks I work with. Can you give a single example where you
found a conflict in libraries that broke your application?

~~~
jamie_ca
Rake::RemoteTask (a dependency of Vlad) defines a bunch of stuff on Kernel,
including #role. For whatever reason, if an ActiveRecord model includes role
as a property, things break in weird ways.

Reasonably easy to work around by aliasing/undefing Kernel#role to
#server_role or something, but amusingly that would be something that could be
fixed by refinements (though I'd rather see something more like Thor, that's
not run toplevel).

------
xentronium
<http://bugs.ruby-lang.org/issues/4085>

Headius technical comments on ruby tracker are a must-read.

As my personal take, I am appalled at refinements. I think this is a bad idea
solving the wrong problem.

~~~
andrewvc
I wish 2.0 had focused on speed, not a bunch of features that working rubyists
really don't care about. Esp. not ones this poorly thought out (you'd think
given 2 years discussion they would have abandoned the idea).

~~~
VeejayRampay
The decision making in the Ruby world seems to be fairly centralized and not
very inclusive. To see that one of core devs of one of the biggest Ruby
implementations thinks it's a bad idea is really confusing to me. I really
wish there had been more discussion about it before it happened.

Cause now not only do we have that confusing feature which very few people
seem to like, it also underlines a worrying trend where the different parts of
the Ruby world don't reach a consensus before going in radical directions.

No one summed it up better than Brian Ford at his RubyConf talk really:
[http://www.confreaks.com/videos/1278-rubyconf2012-toward-
a-d...](http://www.confreaks.com/videos/1278-rubyconf2012-toward-a-design-for-
ruby)

It's a must-see.

~~~
djacobs
While I enjoyed most of Brian's talk, I wasn't particularly convinced by his
dislike of refinements. Essentially, it boiled down to "this feature is hard
to implement, it hides complexity, and it isn't well specified". The first and
third arguments shouldn't matter to us nearly as much as the middle one. And I
fail to see how namespacing monkey patches can do anything but decrease and
contain accidental complexity (in the same way that method dispatch and
polymorphism are better at limiting complexity than explicit conditionals).

~~~
jamie_ca
The benefit to refinements is in preventing code conflicts from a shared
namespace (like monkeypatching a core class).

The downside is it becomes even _harder_ to figure out what code is being run
just by reading it. Right now, you just have superclasses and included
modules, potentially gummed up by method_missing. Now, you need to also pay
attention to refinements included by code that _calls_ the code you're looking
at, which may change the way it behaves.

Unless used with great care, it's going to create a nightmare debugging
situation. And with some of the code I've seen, it'll happen.

------
jonpaul
When it comes to monkeypatching, this is one thing that JS and Ruby got wrong.
C# got it right. It'd be great if you could monkeypatch in JS/Ruby in a module
level scope vs a global scope. Good to see that Ruby is taking steps to remedy
this. Too bad my favorite language, JS, hasn't yet.

~~~
munificent
C# had an easier time here, though, because it's statically typed. Extension
methods rely entirely on static types and static binding.

Getting normal method call syntax while still being lexically scoped like
extension methods without opening the monkey-patching can of worms is still an
open research area as far as I know. I know of classboxes, a proposal for
something similar to ES Harmony (that they ended up passing on) and Ruby
refinements, and that's pretty much it.

~~~
cageface
I had a good ten year run with Ruby and I still enjoy using it for quick
utility scripts but I will never again voluntarily build any large systems in
a dynamically typed language. The benefits of static typing for performance,
correctness, and tooling are just too valuable.

------
MatthewPhillips
What problem does monkey patching solve? It seems to only add complexity onto
the already complex nature of inheritance. What do I gain by adding camelize
to String vs. using MyModule.camelize?

~~~
premchai21
Among other things, MyModule.camelize doesn't provide an extension point for
polymorphism. Consider a serialization library in which different classes
should be able to have separate freeze/thaw behavior. It's possible to do this
in current-day Ruby without easy collisions, but it tends to involve
cumbersome manual prefixing or emulating method dispatch yourself.

Methods being namespaced in packages separately from the class hierarchy is
something CLOS has that I miss in almost all the more Smalltalk-y languages.

~~~
jballanc
MyModule.camelize doesn't support polymorphism (you could even call it
procedural programming in a OOP shell), but something like
Camelizable.new(my_string).camelize does. I'm still convinced that what Ruby
needs is more object composition (and maybe a way to override default literal
construction), and that refinements are a cannon to shoot a mosquito.

~~~
riffraff
Camelizable.new(my_string).camelize does not support polymorphism, because it
does not depend on the type of my_string.

Which means if I want to add camelizable to my AnnotatedString I have to
monkey patch Camelizable#initialize.

~~~
jballanc
No, it means that the implementation of Camelizable#camelize needs to be
polymorphic w.r.t. my_string. Since Ruby is duck-typed, presumably the
implementation of "camelize" would first check to see if the receiver
responded to "gsub", then do the necessary substitutions. (Actually, a better
implementation would be to first check if my_string responds to "camelize" and
call that directly if it does.) In the end, this is a classic decorator
pattern.

~~~
riffraff
if you go the way of a decorator pattern, say, I write my
`IntegerCamelDecorator` since your `Camelizable.new` does not support numbers,
then what is the point of `Camelizable` to start with? You can just have a
mirror hierarchy of `CamelAdapters`.

Of course, you can implement `Camelizable.new` so that it performs a dynamic
dispatch itself, by looking up in a `CamelizableRegistry`. You can even have
this built up magically with reflection.

You can, of course do everything, but a method call in ruby still only
dispatches on self, and if that is fixed the method cannot be polymorphic, if
you think otherwise we can agree to disagree.

~~~
jballanc
You're focusing on the #new call. The actual #camelize call is still
dispatching based on the Camelizable object which is parameterized with
my_string. Ok, so it's not true call-site polymorphism...fine. I'd argue
that's an unimportant implementation detail, but if you prefer (and what I
frequently do in my own code) you could have something more like:

    
    
        Camelizable(my_string).camelize
    

where now Camelizable does the dynamic class lookup to choose the correct
decorator for my_string. This turns #camelize into a truly call-site
polymorphic call and, at least in my opinion, is far more readable/reasonable
than either monkeypatching _or_ refinements.

------
nnq
`module_eval` and `refinements` seem like good ideas unless you mix them. if
`module_eval` wouldn't be so widely used op's arguments about what refinements
do to code readability would mostly fall.

...why not simply just find way to discourage people from using these
together, like having blocks of code only see refinements from their defining
scope, not the one they are actually run in?(this would make it obvious for
library authors that mixing these to features is "not sane to do", if the fact
that they are used to solve similar when it comes to creating DSLs problems
does not make it obvious)

------
pirateking
Does anyone know how Objective-C categories handle some of the issues brought
up with Ruby 2.0 refinements?

It seems method swizzling is a far less touched trick than monkey patching,
and the latest LLVM compiler throws a warning if a category overrides an
existing method. I am curious about low level differences in how the two
languages approach this problem.

~~~
sha90
Method swizzling and refinements are basically the same thing. The real
difference isn't in the features, it's in the fact that Ruby is a dynamic
language, and Obj-C is only _dynamically typed_. You can swizzle all you want
in a statically compiled program, and refinements work equally well in a
"static" Ruby program. The problems with refinements start coming to light
when you start refining things at runtime, i.e., with module_eval. Obj-C
doesn't have this problem, since swizzling happens only once at compile time.

~~~
pirateking
I don't believe that is true, you can use class_replaceMethod to dynamically
swizzle methods at runtime. This gets rid of compiler error and warning
messages that appear when just using categories to override existing methods.
From my understanding, the compiler enforces compilation unit visibility for
methods implemented in a category, but the methods actually are actually added
to the global scope.

There is no "top level" object or class_eval in Objective-C, so the situation
is quite different from Ruby.

Here is an interesting paper about classboxes[1] (same concept as
refinements), and the source code for the Objective-C runtime[2]. I found them
to be useful in my research.

[1]
[http://rmod.lille.inria.fr/archives/papers/Berg05a-CompLangE...](http://rmod.lille.inria.fr/archives/papers/Berg05a-CompLangESUG04-classboxesJournal.pdf)

[2]
[http://opensource.apple.com/source/objc4/objc4-532.2/runtime...](http://opensource.apple.com/source/objc4/objc4-532.2/runtime/objc-
runtime-new.mm)

------
danso
While I run into unexpected monkey-patching from time to time (i.e. just about
anytime I include an "active_" library from Rails), I can't remember the last
time it's screwed me over. But I can see the potential...what have been the
most prevalent cases of conflicts for day-to-day Rubyists?

~~~
knowtheory
Merb & DataMapper's integration with Rails 2 was seriously hindered by
ActiveSupport's core monkey patching, coupled with how ActiveRecord was
inextricably linked with other Rails components.

As a consequence, it really took Rails3's clean up of ActiveSupport to allow
for other ORMs and libs to integrate in a safe manner.

I'm not 100% certain about the way that refinements currently work (mainly
because I haven't had time to dig into the proposal), but the case that wycats
makes for refinements amount to defensive coding. When you chose to include a
lib, you should be able to control whether and how it steps on the rest of
your app (and w/ refinements that can be done by specifying for other parts of
your app, that it should/n't listen when someone tries to monkey patch its
stuff).

~~~
vidarh
One of the things that terrify me about refinements is that it'll just end up
as a band aid for bad designs in cases where the vastly better solution would
be to redesign without the need for monkey patches. They we end up with code
that's potentially a lot harder to reason about out of pure laziness.

