If it walks like a duck and it quacks like a duck
Then it must be an IQuackFu.
IQuackFu is Boo’s answer to the Method Missing / Message Not Understood from dynamic languages. Since Boo is a statically typed language[1], and since method missing is such a nice concept to have, we use this special interface to introduce this capability.
You are probably confused, because I didn’t even explain what method missing is. Let us go back and look at an example, shall we? We want to look at the following xml:
<People> <Person> <FirstName>John</FirstName> </Person> <Person> <FirstName>Jane</FirstName> </Person> </People>
Now we want to display the first names in the xml. We can do it using XPath, but the amount of code required makes this awkward. We can also generate some sort of strongly typed wrapper around it, assuming that we have a schema for this, we can use a tool to generate the schema, if we don’t have it already…
Doesn’t it look like a lot of work? We can also do this:
doc = XmlObject(xmlDocument.DocumentElement)
for person as XmlObject in doc.Person:
print person.FirstName
But we are using a generic object here, how can this work? This works because we intercept the calls to the object and decide how to answer them at runtime. This is the meaning of the term “method missing”. We “catch” the method missing and decide to do something smart about it (like returning the data from the xml document).
At least, this is how it works in dynamic languages. For a statically typed language, the situation is a bit different; all method calls must be known at compile time. That is why Boo introduced the idea of IQuackFu. Let us check the implementation of XmlObject first, and then we will discuss how it works:
class XmlObject(IQuackFu):
_element as XmlElement
def constructor(element as XmlElement):
_element = element
def QuackInvoke(name as string, args as (object)) as object:
pass # ignored
def QuackSet(name as string, parameters as (object), value) as object:
pass # ignored
def QuackGet(name as string, parameters as (object)) as object:
elements = _element.SelectNodes(name)
if elements is not null:
return XmlObject(elements[0]) if elements.Count == 1
return XmlObject(e) for e as XmlElement in elements
override def ToString():
return _element.InnerText
We didn’t implement the QuackInvoke and QuackSet, because they are not relevant to the example at hand, I think that QuackGet will make the point. Now, just to complete the picture, we will write the first code sample, the use of XmlObject, as the compiler will output it.
doc = XmlObject(xmlDocument)
for person as XmlObject in doc.QuackGet(“Person”):
print person.QuackGet(“FirstName”)
The way it works, when the compiler finds that it can’t resolve a method (or a property) in the usual way, it then check if the type implements the IQuackFu interface. If it does implement IQuackFu, it translates the method call into the equivalent method call.
The example of the Xml Object is a really tiny one of the possibilities. Convention based methods are an interesting idea[2] that is widely used in Ruby. Here is an example that should be immediately familiar to anyone who dabbled in Rails’ ActiveRecord:
user as User = Users.FindByNameAndPassword(“foo”, “bar”)
Which will be translated by the compiler to:
user as User = Users.QuackInvoke(“FindByNameAndPassword”, “foo”, “bar”)
The Users’ QuackInvoke method will parse the “method name” and issue a query by name and password.
You can do some very interesting things with IQuackFu...
[1] Well, it is statically typed unless you explicitly tell the compiler that you want late bound semantics. Aside from working against IDispatch COM interfaces, I have rarely found that ability useful. One case I did find it useful, however, was when I wanted to introduce Context Parameters, which we will discuss in a few pages.
[2] For the adventurous sorts, you can also do something called Lazy Methods, in which you generate a method if and only if it is being called. This is an interesting exercise in extending the compiler, but for all intents and purposes, IQuackFu answers this need very well.
Comments
I haven't checked, but I'd be surprised if the DLR would not already support such a mechanism ("already" is a bit much for a prerelease that has its design changed on a daily basis, but you get the idea. but still, that would give you cross-langugage capabilities, while IQuackFu would not)
Stefan,
I highly doubt that it would. The DLR is just the platform, the policy, the part that decide how to do the method dispatch, is in the language hands.
So how would Ruby code call a Python method if there was no common interface for dynamic calls? Just browsed through Jim Hugunin's blog, I think IDynamicObject is it:
http://blogs.msdn.com/hugunin
Stefan,
Sorry, I was wrong.
Yes, it looks like it does the same thing.
I love the "method missing" concept, but why did boo decide to name the interface "IQuackFu"? I may just be boringly conservative, but I think that silly names tend to work against a language being taken seriously, and although I haven't actually used boo, from what I've read on your blog I would love to see it gain wider acceptance.
jr76,
Well, because it quacks like a duck and to use it you have to know some Boo Fu fighting.
I don't think that this is a reasonable amount of levity in a language is to be discourage.
I do wish IQuackFu or its equivalent were supported in C#. It seems like a relatively simple compiler modification and even Intellisense support for it (i.e. just not complain on any call for a IQuckFu implementer) doesn't seem too hard.
Arne,
I believe there is some COM-interop magic in C# that handles duck typing, it might be possible to abuse it in order to target IQuackFu or the DLR. But then again, this magic might be best forgotten...
Stefan
Comment preview