Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Yehuda Katz's 10 Favorite Things About Ruby (yehudakatz.com)
42 points by wifelette on Aug 24, 2009 | hide | past | favorite | 25 comments


Unfortunately try illustrates the exact problem with open classes so many non-Rubyists are afraid of: collisions.

try was originally defined as:

  class Object
    def try(method)
      send method if respond_to? method
    end
  end
  
In Rails it is defined as:

  class Object
    alias_method :try, :__send__
  end

  class NilClass 
    def try(*args)
      nil
    end
  end
The following code will exhibit different behavior depending on which version of try you're using:

  [].try :upcase
For example:

  # original try
  >> [].try :upcase
  => nil  

  # rails try
  >> [].try :upcase
  NoMethodError: undefined method `upcase' for []:Array
If Rails uses try internally, and a Rails plugin you've loaded depends on the original version of try, what now? They both behave differently and, presumably, code using them depends on their specific behavior.

Also don't forget the dozen or so other versions of try people have added to their own plugins and apps...


Articulated perfectly! Any time I'm working on a Rails project, and run into odd behaviours, the first thing I'll do is check to see if I'm actually using a Ruby or a Rails method. Most often than not, I'm expecting a "Ruby" way of doing things, but find out I'm actually using the "Rails" version, which does something completely different. If I run into any in the next few hours, I'll post them here :)


It's not really a Ruby vs Rails way of doing things. The Rails implementation is arguably more in line with the standard Ruby behavior since it throws a NoMethodError for everything other than Nil objects.


I would argue that, no, there isn't "standard Ruby behavior" in this instance. It's just a method.


True, they are all just methods, but it's basically just an altered version of Object#send, and the current Rails implementation only significantly differs from it in the specific situation it's intended for.

I realize there are a range of preferences regarding ways to tackle the problem this is intended to solve, and I'm relatively agnostic about the overall issue, but if the implementation is basically a #send that guards against nil, then it would be unexpected for it to deviate remarkably from #send when dealing with non-nil objects.


Another difference: the original version doesn't take multiple arguments, but the new one does.


This was supposed to be a really simple example. One could always write the code such that try only gets defined if there's no existing definition.


Again: you are using two libraries (Rails and plugin X) that depend on their own versions of try, which they include.

One of the libraries is going to break.


Indeed. It's probably wise to use namespaced internal methods inside of libraries or frameworks. However, Rails has historically pioneered new language features that eventually make their way into the language itself (Object#tap, Enumerable#group_by, etc. etc.).

In Rails 3, we've made it reasonable to pull in just small pieces of the ActiveSupport library, making it more likely that users will reuse functionality from there rather than reinvent the wheel.

Of course, it's not perfect.


Am I right in thinking that once a piece of ActiveSupport is pulled in anywhere in the codebase it will affect ALL Ruby objects, rather than having its effect scoped to just the module that included it?


Yep. You'd be modifying the String class itself, not just Strings that find their way into some file.


Well... yes. You're right. If you're depending on one behavior rather than the other, then totally. I guess I was thinking about it more in terms of two different implementations of the same thing. "If we don't have a quicksort defined yet, make one."


This is basically how ActiveSupport's "activesupport/ruby/shim" functionality works. It adds pure-ruby versions of features added in Ruby 1.9 only if they aren't already defined.


I love reading articles like this. 8 years or so ago Ruby replaced Perl for me as my 'go to' language for day-to-day problems. I haven't looked back since.

What other people call 'magic', I call not understanding the language and execution environment.

For long-running server-side stuff Ruby would not be my first choice, but for eloquence and scripting capabilities... accept no substitute.


Not everything in Ruby is a full-fledged class; for instance, you can't extend Fixnum.

[I am apparently completely wrong here].


Says who?

irb(main):001:0> class Fixnum

irb(main):002:1> def doit

irb(main):003:2> puts "hey"

irb(main):004:2> end

irb(main):005:1> end

=> nil

irb(main):006:0> 2.doit

hey

=> nil

irb(main):007:0>


Yup. Perhaps he was thinking of Python, where his assertion is true.


Not true in Python either since new-style classes unified types and classes.


  class Fixnum
    def +(other)
      42
    end
  end
3 + 2 => 42


I try to sneak this into all of my rubygems


Ah The Answer. BTW, it crashes my IRB (Ruby 1.9.1/Win)


You're partly right. Fixnums don't have a metaclass (or virtualclass) because they're actually "immediate". So you can't say 123.extend(Something) because 123 isn't really a full Object.


That's just to say that there aren't multiple "2"s. But 2 itself is still an object, pointing at its Fixnum class, which has all the normal Class and Object semantics.


Fixnums have a different representation in the interpreter (at least in 1.8 MRI) than normal objects do, which is probably what tripped me up here.


Yeah. Conceptually, you can think of the difference as whether or not there can be two copies of something. There can only be one copy of a Symbol, Fixnum, true, false, and nil. This means that true == true, and true.dup #=> error.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: