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?
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.
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.
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.
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.
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.
`5.days.ago` is actually harmful IMO. You get used to stuff like that, and then (if you're me at least), you basically forget how to perform basic date/time operations without it. Is +int on a Date referring to Day? Month? Year? What about on a Time object?
Custom percent-expressions would be a more elegant solution I think, allowing you to embed truly distinct mini-languages in Ruby: %date(5 days ago). Or %time(-12 hours), etc.
You could always overwrite modulo on classes that didn't need it: `Date % "5 days ago"`. You lose syntax highlighting that way though.
I guess my point is, there are other ways. I've put 5 minutes of effort into these alternatives. Monkey Patching can be a harmful crutch.
Though OTOH, these days I think Ruby would be entirely better off if it just included ActiveSupport into stdlib, and it could benefit from the optimizations available as a first-class Ruby library. It's basically defacto, and despite being involved with a couple different alternatives, I think that'd be best for everyone.
At the end of the day, I think if you want to make a strong argument for/against monkey-patching, it's best to leave ActiveSupport out of the discussion. It's so prevalent, it's hard to make a general case either way since pretty much everyone is aware of how to avoid stepping on it.
Your gist is exactly how sequel does it. However, I think having optional (!) syntax for extending symbols is absolutely fine as long as your library does not depend on it.
Regarding including ActiveSupport in stdlib, I see an opposite trend of extracting everything from stdlib into gems because it's hard to provides updates to anything in stdlib. Let's leave it as it is.
> Custom percent-expressions would be a more elegant solution I think, allowing you to embed truly distinct mini-languages in Ruby: %date(5 days ago). Or %time(-12 hours), etc.
The issue there being that 1. it's going to become a pain to parse fast and 2. it starts looking a lot like genuine text, and thus people will want it localizable, thus bringing more of 1
ssmoot says he invented this, which is eminently readable but feels like being too leaky (what's the name of the deprecated gem that does that to AR already?):
1) User.where(:age.gt => 5)
ssmoot says this is better:
2) User.where { |user| user.age > 5 }
SQLAlchemy does it this way:
3) query.filter(User.name == 'ed')
which looks so much like how AR3.0 + ARel does it (and seriously AR::Base should delegate [] to arel_table):
4) User.where(User.arel_table[:age].gt 5)
and here's squeel's way:
5) User.where{age >= 5}
FTR, Django QuerySet does it by parsing kwargs (convention is double underscore => dot, which allows to 'call' methods on fields (pub_date__year) and joined relations (group__name)):
6) User.objects.filter(age__gt=5)
From a 'user' (i.e developer consuming the API) perspective I really like 1 and 5 because there is no redundancy (with which 2 is full of). Still when you're join()ing, you have to make ambiguous things explicit, and in that case 1 falls apart (you're not going to write :group_name, are you?), so while 5 is nice and allows for niceties like group.type, I am perfectly content with 4 because it's both stock Rails and quite readable, it's just that the full sized User.arel_table breaks the reading flow, and a bit redundant in the trivial case.
I'm not fond of stringifying stuff as resorting on parsing makes it less dynamic and more prone to abuse, and it we write strings we might as well write partial SQL.
Indeed ActiveSupport is a bad example because it extends by monkeypatching, but implements stuff that always apply (all strings are potentially camelizable, always, ever, and arguably integers can always be qualified with units) whereas Symbol#gt is nonsensical outside some specific scope.
The refinement situation is terrible because it performs poorly, leaks badly and makes things terribly inconsistent, especially with blocks passed around. I can't even begin to fathom the consequences of a block being called on an unexpected binding.
> `5.days.ago` is actually harmful IMO. You get used to stuff like that, and then (if you're me at least), you basically forget how to perform basic date/time operations without it.
that's not harmful - it's pretty useless to remember a worse way to do things, especially since you can always just pull in a dependency on activesupport or even paste in that piece of code into your own project and then continue using .days.ago.