Refining Ruby
blog.headius.com
blog.headius.com
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?
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.
While it's not incredibly common, disrespecting namespace concerns is a sure road to problems.
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).
Maybe refinements should evolve into some MRI diagnostics mode.
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.
class Foo < SomeParent
def baz(str)
cached = str.camelize
ary.map {|name| cached + name}
end
end
Could have a completely different meaning than this code: class Foo < SomeParent
def baz(str)
ary.map {|name| str.camelize + name}
end
end
/lights hair on fireHopefully a lot of the tweaks and refinement will come from now to Final 2.0 release.
LuaJIT is pretty much impossible in a language with as complex semantics as Ruby (it's probably impossible in Python already, and Ruby is significantly more complex)
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...
It's a must-see.
http://www.confreaks.com/videos/1275-rubyconf2012-ruby-2-0-o...
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.
http://igor-alexandrov.github.com/blog/2012/11/05/yet-anothe...
I don't much like refinements, but conflicting monkey patches from different third party libraries _has_ been an issue over the years.
The ruby community is not even the first one to notice that, there is plenty of literature on "selector namespaces" and "classboxes" from the smalltalk crowd.
In my eyes, monkey patching should only ever be considered:
1. To fix outright bugs or nasty performance issues, where the monkey patch should not have other side effects.
2. In application code, never in libraries (except as a library explicitly providing monkey patches to an application, but never as a requirement for a library to work).
3. In adding new methods, except for case 1.
Of course there'd be exceptions, but very little monkey patching I see in library code is necessary or worth it.
Inside your library, you have plenty of ways of avoiding the need: Wrap objects; convert objects; use helpers. Yes, it might not look as perfectly smooth, but I'd take that over trying to reason about code that relies on different sets of refinements in different scopes any day..
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.
Which is a neat way to go about it.
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).
The bad thing is, most of the issues would be caught quickly if we could run stuff with -w, but nowadays nobody uses and you'll get floded with warnings from the libraries you use :(
[0] http://yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-pra...
[1] http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/...
[2] http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectatio...
"foo".camelize
reads better than: MyModule.camelize "foo" 5.days.ago
User.where(:age.gt => 5)Something better might look like: https://gist.github.com/4113718
`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.
Looks quite similar to how SQLAlchemy does it, although it doesn't use a block: http://docs.sqlalchemy.org/en/rel_0_7/orm/tutorial.html#comm...
> 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
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.
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.
Btw, are you the original author of DataMapper?
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.
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.
Which means if I want to add camelizable to my AnnotatedString I have to monkey patch Camelizable#initialize.
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.
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....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)
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.
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...
[2] http://opensource.apple.com/source/objc4/objc4-532.2/runtime...
> swizzling happens only once at compile time.
Objective-C is a dynamic language in every sense of the term.
As examples, at runtime, you can:
- change an existing class' superclass - swizzle methods - create a class ex nihilo (though it'd be a better idea to inherit from NSObject) - give that class (or even an existing one) methods, ivars, and properties
.. and much more
Visit the runtime reference[1] and jump into a running Objective-C app with GDB or F-Script. It's a lot of fun.
[1] https://developer.apple.com/library/mac/#documentation/Cocoa...