An overview of the Boo language
35Boo’s built-in language-oriented features
So far, this sounds a lot like late-bound semantics (similar to VB.NET’s behavior when you set option strict off), but there’s more. By default, when you ask Boo to use duck typing, you’ll get late-bound semantics, but IQuackFu also allows an object to take part in the decision about where to dispatch a call that cannot be made statically.
Don’t worry if this doesn’t quite make sense yet—it’ll be clearer once we look at an example. Suppose we want to extract and display the first names from the XML in list-ing 2.7.
<People>
<Person>
<FirstName>John</FirstName>
</Person>
<Person>
<FirstName>Jane</FirstName>
</Person>
</People>
We could do this using any number of XML processing options, but the amount of code required would make this awkward. We could also generate some sort of strongly typed wrapper around it if we had a schema for the XML. Or we could use a tool to gen-erate the schema if we didn’t have it already ... This is starting to look like a lot of work.
We could also do it as in listing 2.8. We’re using a generic object here, so how can this work?
doc = XmlObject(xmlDocument.DocumentElement) for person as XmlObject in doc.Person:
print person.FirstName
The code in listing 2.8 works because we intercept the calls to the object and decide how to answer them at runtime. This is what method missing means. We catch the call to a missing method 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’s why Boo introduced IQuackFu. Take a look at the implementation of XmlObject in listing 2.9, and then we’ll look at how it works.
Listing 2.7 The input XML structure
Listing 2.8 Using the XML object to output
Figure 2.1 The IQuackFu interface is Boo’s version of method missing.
class XmlObject(IQuackFu): # Implementing IQuackFu interface _element as XmlElement # The element field
# Get the XML element in the constructor and store it in a field def constructor(element as XmlElement):
_element = element
# Intercept any property call made to the object.
# This allows us to translate a property call to navigate # the XML tree.
def QuackGet(name as string, parameters as (object)) as object:
# Get the node(s) by its name
elements = _element.SelectNodes(name)
if elements is not null: # Check that the node exists # Here we're being crafty. If there is only one node # selected, we'll wrap it in a new XmlObject and # return it. This allows us easy access throughout # the DOM.
return XmlObject(elements[0]) if elements.Count == 1 # If there is more than one, return a list of all
# the matched nodes, each wrapped in an XmlObject.
xmlObjects = List[of XmlObject]() for e as XmlElement in elements:
xmlObjects.Add( XmlObject(e) ) return xmlObjects
else:
return null
# We don’t support invoking methods, so we ignore this # method. We could also raise NotSupportedException here.
def QuackInvoke(name as string, args as (object)) as object:
pass # ignored
# If we wanted two-way communication, we could have built # it into this method, but we don't, so we ignore this # method as well.
def QuackSet(name as string, parameters as (object), value) as object:
pass # ignored
# This is to make it easier to work with the node.
override def ToString():
return _element.InnerText
This code doesn’t implement the QuackInvoke and QuackSet methods, because they aren’t relevant to the example. Implementing QuackGet is sufficient to make the point.
Listing 2.10 shows how we could use the XmlObject if we didn’t have the compiler doing it for us.
Listing 2.9 The XML object implementation—method missing over an XML document
37 Summary
doc = XmlObject(xmlDocument)
for person as XmlObject in doc.QuackGet("Person"):
print person.QuackGet("FirstName")
When the compiler finds that it can’t resolve a method (or a property) in the usual way, it then checks whether the type implements the IQuackFu interface. If it does, the compiler translates the original method call into a method call using QuackGet, QuackSet, or QuackInvoke. This means that we get to decide what will happen at run-time, which allows us to do some nice things. The XmlObject example is just one of the possibilities.
Convention-based methods are an interesting idea that’s widely used in Ruby.
Here’s an example that should be immediately familiar to anyone who has dabbled in Rails’ ActiveRecord:
user as User = Users.FindByNameAndPassword("foo", "bar")
That will be translated by the compiler to this:
user as User = Users.QuackInvoke("FindByNameAndPassword", "foo", "bar")
The User’s QuackInvoke method will parse the method name and issue a query by name and password.
This is a neat trick, with serious implications for writing DSLs. IQuackFu is usually the first tool that I reach for whenever I find that the standard mechanisms of the lan-guage aren’t sufficient to express what I want.
There are also other tools that are useful, but we’ll learn about them in chapter 6.
2.5 Summary
We’ve gone over the major benefits of Boo as a language for creating DSLs: its basic syntax and some of the things we can do with the built-in features.
In general, when building DSLs, I consider all of the aspects of Boo we’ve covered so far as the low-hanging fruit: easy to pick, good, and nearly all you need. You can build good DSLs by utilizing what the language gives you, without digging into Boo’s more advanced options. Even when you do need to use some advanced options, you’ll still need those regular language concepts in most of the DSLs you build. I recom-mend that you learn these options and think about how to use them effectively. But the basic Boo features aren’t always enough, and they’re certainly not the sum of what you can do using Boo.
I have chosen not to bore you with the usual language details, such as introducing if statements and explaining how loops work. Boo is simple enough that it shouldn’t be too difficult to grasp the fundamentals based on the examples in this chapter.
Because Boo runs on the CLR, it uses a familiar environment, so you’re spared from learning that as well.
Listing 2.10 Manually calling IQuackFu methods
If you want a more structured approach to learning the language, take a look at appendix A, which contains a Boo language tutorial. Appendix B contains a syntax reference, which will allow you to translate between C# and Boo. I recommend at least skimming through appendix B.
As for reading Boo code, the overall structure of Boo means that it’s similar to C#.
In both you have classes, methods, operators, control statements, and so on. If you can read C# code, you can read Boo code. More information about Boo the language can be found on the Boo site (http://boo.codehaus.org/) and in the Boo language guide (http://boo.codehaus.org/Language+Guide).
We’ll defer the dive into the deep end for a bit, and first discuss building an appli-cation with a DSL. Once we’ve covered the underlying concepts, we’ll crack open the compiler and start telling it how it should behave.
For now, the next step is building a DSL-driven application.
39