6 ms·
Do you know Ruby’s ‘Chainsaw’ method?
- deleted 16y ago[deleted]
- zeteo 16y agoFP fanboys will laugh so hard at this. The proper way to deal with duplication, as described in this article, is of course to use functors. With a general method_missing(), your compiler will not even catch spelling errors anymore.
- epochwolf 16y ago> With a general method_missing(), your compiler will not even catch spelling errors anymore. Ruby isn't compiled. Whether or not it catches typos is up to your logic. All of my method_missing calls eventually hand off to the parent class's implementation when they can't match anything. > The proper way to deal with duplication, as described in this article, is of course to use functors. Except this is ruby. Ruby has modules to deal with duplication between classes and code blocks (closures, which can be bound to variables) for general use. It also allows for a rather clean implementation of the proxy design pattern via method_missing. Anything dealing with classes assumes OOP, not FP. I only have begun to figure out that I don't know anything about FP but your comment seems rather absurd in this context.
- jhickner 16y agoThere's actually a fairly safe way to use method_missing. Just remember to call super if none of your conditions are met, that way you'll still get errors when commands are misspelled. You really shouldn't ever use method_missing without that. He probably also should be checking if @desk actually has a method with the provided name (with @desk.respond_to?(method_name)) before attempting to call it.
- epochwolf 16y agoWhen using method_missing in a proxy why bother checking? Just call the method and let wrapped object handle the errors. The stack trace will show you where to look for the error. The only reason I can see to check before calling is if you have different failure mode than the wrapped object. Otherwise you are duplicating logic.
- jhickner 16y agoIt's a small thing, I admit, but I like getting a slightly cleaner stack trace since the exception happens in the wrapper module (where the error is) instead of getting all the way to @desk.
- epochwolf 16y agoAfter I've spent the last 2~3 years reading funky rails stacktraces something like that doesn't bother me a bit. (I get more worried about older versions of jruby deciding to puke on internal ruby<->java mismatches at work) :)
- tomafro 16y agoHe should also define respond_to? on the DoNotDisturb class. Relying on method_missing to pass calls to respond_to? to the proxied class will not work, as it is defined in Object. Responding to a method when respond_to? returns false breaks the class contract.
- nusco 16y agoFrom the OP: I agree with you guys (or gals) on both calling super and redefining respond_to?(). Those are two of the "caveats" I mentioned at the end of the original post. I considered mentioning them explicitly, but the post is long enough already.
- JangoSteve 16y agoI prefer to use define_method over method_missing when "metaprogramming". It allows you to explicitly define your "metamethods" up-front. Using his example of the DoNotDisturb wrapper around InformationDesk, you could easily do something like: [:emergency, :flights, :local_transports, :hotels].each do |name| define_method(name) do check_lunch_break unless name == :emergency @desk.send(name) end end Now you don't get the side effect of screwing up your exception handling and having to walk on eggshells with the rest of your coding in that class. The other thing I like about this method is that it gets evaluated once, and then those methods exist. With method_missing, every single time you call one of those methods, it has to go all the way up the chain to find that method until it finally hits method_missing. EDIT: Oh, and if nothing else, you can at least put the define_method inside the method_missing (I had a project that required method_missing once, so this is what I did). That way, it only has to crawl all the way up the inheritance chain once, then that method gets defined on your class, so subsequent calls to that method don't have to go all the way up the chain again. Might not be much performance gain in Ruby, but when programming a rails app (where you're likely working on a class that inherits from ActiveRecord and 10 other classes), it helps.
- some1else 16y agoYour approach of defining, even within method_missing is better. Defined methods instead of method_missing is also how Rails 3 got some of it's major speed boosts.
- JangoSteve 16y agoOh, and another option would be to pull the common methods out into a module that you can include in each class with one line. I like this option, because then it’s trivial to go create another class called OutForVacation, in which you could include this module, and now you have another wrapper with all the same functionality.
- Groxx 16y agoThat makes a ton of sense. It would also aid significantly in debugging, as you can see every method you created, rather than only being able to infer what they might be.
- jfager 16y agoWhen I work with Java or C#, I accept this kind of code duplication as a fact of life. Java has had dynamic proxies since 1.4. As long as you're working with interfaces, you can do the exact same thing as the example given in this post.
- city41 16y agoAnd the new dynamic support in C# gives it a direct equivalent to method_missing.
- nusco 16y agoFrom the OP: I used dynamic proxies in both Java and C#. For example, I wrote the annotations introspector for Hibernate Annotations, and a memcached wrapper for a C# project. Both are proxy-based. However, using dynamic proxies in Java feels like going against the grain of the language: strong typing and dynamic calls don't really like each other. As a result, you usually resort to Java/C# dynamic proxies only when you have no other option. I've yet to see anyone using a dynamic proxy just to remove duplication in a Java class. C# dynamics might make dynamic proxies less clumsy in that language. Will try. Do you have experience with them already?
- jfager 16y agoI haven't seen them used much in any of the open source Java projects I'm familiar with, I agree. We used them fairly extensively at my previous job, though, for implementing a mix-in pattern for boilerplate-free service endpoints, and they worked nicely. Outside of that, I use them al the time during development to add quick-and-dirty method call tracing when I'm trying to debug something. I personally think that they're very much underused, and having based an interview question on them for a while, not well-known at all.
- joshd 16y ago"I use method_missing()to remove duplication ... On the other hand, I usually think twice about using method_missing() for cosmetic reasons, like getting cool method names such as find_by_name_and_address()." That seems backwards to me. I use magic methods to define methods that can only be defined at run time, like his example of creating methods based on schema introspection. Using magic methods breaks a lot of tools: IDE code completion, reflection, documentation generation so I always explicitly define methods unless it's not possible.
- xentronium 16y agoFor me method_missing looks more like an anti-pattern because it's easy to miss, it's hard to debug, it's impossible to scan visually, it messes up code lookup if you use IDE, it messes up search if you don't.
- bluesnowmonkey 16y agoA better analogy to magic methods would be dynamite. There are a few situations where you should use it, but not many, and generally it's when there's no other option. His example violates the principle that objects should represent things. (What is a DoNotDisturb?) Back to OOP school, metaprogrammer!
- nusco 16y agoFrom the OP: I considered changing the names because I had the same concern, but in the end I figured that the current name was good enough for a toy example. In hindsight, you're probably right I should have used other names (although your sneering tone is not really necessary).
- jrockway 16y agomethod_missing / AUTOLOAD is a great way to break any tools that rely on being able to introspect your classes. For something as simple as this, just add the methods to the class in a loop. Then you class works just like every other class; no introspection is broken. It's also faster at runtime.
- tomafro 16y agoI can see how method_missing can break introspection, but I don't see how the same is true for Autoload. Could you give an example?
- draegtun 16y agoI believe jrockway is referring to AUTOLOAD in Perl (ie. its equivalent of Ruby's method_missing). ref: http://perldoc.perl.org/perlsub.html#Autoloading http://perldoc.perl.org/perlsub.html#Autoloading
- scott_s 16y agoI don't do any Ruby, but: can someone give a non-contrived situation where you would want to do this? What problem does this solve?
- jfmiller28 16y agoThe most sited example is ActiveRecord where finders can be auto-generated. For example `find_by_name_and_address` will be defined on the fly the first time it is used in code. Another common usage is a wrapper class that provides a unified interface to a set of underlying objects. If many of these objects have almost the same interface `def method_missing(method,args); @object.send(method, args);end` can save a lot of redundant definitions.
- draegtun 16y agoGood example would be XML/HTML builder. For eg in Ruby http://builder.rubyforge.org/ http://builder.rubyforge.org/ require 'rubygems' require_gem 'builder' builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2) builder.person { |b| b.name("Jim"); b.phone("555-1234") } # Prints: # <person> # <name>Jim</name> # <phone>555-1234</phone> # </person> Also see this recent HN thread: http://news.ycombinator.com/item?id=1626018 http://news.ycombinator.com/item?id=1626018
- scott_s 16y agoAnd why would you do this instead of just defining a print method that takes the XML tag and content as separate parameters?
- pkulak 16y agoI've found that the only good time to use method_missing is when I'm wrapping an object and need to send all but a couple methods through to it untouched. I've really never used it for anything else.
- callmeed 16y agoI'm going through the author's book right now (Metaprogramming Ruby). It's great.