Chapter 2. An overview of the Boo language

In this chapter

  • Exploring the Boo language
  • Writing a sample program
  • Built-in DSL-friendly features

What is this Boo language anyway?

Boo is an object-oriented, statically typed language for the Common Language Runtime (CLR) with a Python-inspired syntax and a focus on language and compiler extensibility. (We’ll discuss extensibility briefly in this chapter and in more detail in chapter 6.) Boo is an open source project released under the BSD license, which means you’re free to take, modify, and use the language and its products in any way you want, without limitation, including for commercial use. (The license can be found at http://svn.codehaus.org/boo/boo/trunk/license.txt.)

Rodrigo B. de Oliveira started the project in early 2004, and since then it has grown significantly in popularity and features. The project is active and is continually evolving. As I write this, the released version of Boo is 0.9.1.


Getting Boo

You can download the binary distribution of Boo from the Boo website: http://boo.codehaus.org/.

You can execute booish.exe from the bin directory (either by double-clicking it in Windows Explorer or issuing the command on the command line) to get a command interpreter that will allow you to experiment with Boo directly. It’s convenient when you want to quickly try out some ideas, or verify an assumption.

There is also booi.exe, which allows you execute Boo scripts without an explicit compilation step. And the compiler itself is invoked using boo.exe much as you can invoke the C# compiler by executing csc.exe.



Note

Boo seems to be following the trend of many open source projects in that it isn’t rushing toward a 1.0 release. Boo has been stable for a number of years, and I have been using it for production for the last 4 years. I have a high degree of trust in Boo, and it has not failed me yet.


Now that you know what Boo is, you’re probably wondering why I chose to use it.

2.1. Why use Boo?

Boo is a CLR-based language, which means that when we use Boo, we benefit from the rich library that comes with .NET, JIT (just-in-time compilation) optimizations, and plenty of good tools. Even without extending the compiler, Boo has the following to offer:

  • Syntactic sugar for common programming patterns— List, hash, and array literals, object initialization, string formatting, and regular expression matching are all first-class concepts in Boo. There is direct support for all of them in a natural manner.
  • Automatic variable declaration and type inference[1] The compiler takes care of things for you, so you don’t have to type the same thing over and over again. Some would say that this is bad, due to the lack of explicit typing, but it’s worth trying. In my experience, it works. Take a look at listing 2.1.

    1 C# 3.0 has introduced the var keyword, which allows the C# compiler to do much the same for local variables. Boo supports this for just about everything (fields, properties, methods, local variables, and so on).

Listing 2.1. Exploring Boo’s type inferencing
def random():
return 4 # Selected by dice roll, guaranteed to be random

val = random()

Why specify the type over and over again? The compiler can figure it out for itself and not bother you with it. If you want to get a compiler error, you can explicitly specify the type, as in listing 2.2.

Listing 2.2. Boo is statically typed—this code will cause a compiler error
val as string = random() # Will give error about type mismatch

  • Automatic typecasting— The compiler will automatically cast variables to the appropriate type (if this is possible), without forcing you to explicitly cast variables. For example, when you try to pass a variable of type Animal to a method that requires a dog instance, the compiler will automatically cast it to a dog. The premise is that you would have to do this anyway. The following example shows what automatic typecasting does:
    animal as Animal = Animal.CreateDog("Spot")
    dog as Dog
    dog = animal # Will automatically cast animal to Dog
  • Duck typing— Boo is a strongly typed language, but you can ask the compiler to relax those constraints in certain situations and work like a dynamic language. This makes some things much more natural, because you can get into this dynamic invocation infrastructure and decide what to do at runtime. C# 4.0 will have some of the same capabilities when it comes out.

Let’s see what we can do with the XmlObject in listing 2.3.

Listing 2.3. Using IQuackFu to get better syntax for working with XML
person = XmlObject(xmlDocument)
print person.FirstName
print person.LastName

This example resolves, at runtime, that we were asked to get the value of a property called FirstName, and then a property named LastName. This is easy to implement, as we’ll see in section 2.4.9.

These features take care of some rough spots you might encounter when using string-based APIs, and they can be useful when building languages on top of Boo.

2.2. Exploring compiler extensibility

Boo is far more flexible than most other languages, and the option to reprogram the compiler is valuable not only for creating DSLs, but for taking care of tedious tasks that require repetitive code.

For example, suppose you want to implement the Singleton pattern. You’d need to do several things to make it happen:

  • Ensure that the creation of the singleton is thread-safe
  • Lazily initialize the singleton
  • Make all the constructors private
  • Ensure that serializing and deserializing the singleton will not create several instances of the singleton
  • Create a static Instance property
  • Have no static members on the singleton class

To do this in most languages, you’d have to follow an implementation pattern and write fairly repetitive code. In Boo, we can capture this implementation pattern in an AST attribute that will do all of that for us (more on AST attributes in chapter 6). Here’s how you would define a singleton in Boo.

[singleton]
class MySingleton:
pass

The [singleton] attribute isn’t a built-in capability of Boo; it’s a standard extension, and the capabilities to extend the language are also exposed to our code. This makes writing DSLs much easier.

By using a similar approach and the same language-extension capabilities, you can use [disposable], [observable], and other advanced techniques, such as aspect-oriented programming.


Note

There’s lots to learn about Boo, but this book will focus on the things we need for building DSLs. You can find more information about useful things you can do with Boo here: http://docs.codehaus.org/display/BOO/Useful+things+about+Boo.


But before we discuss all the hoops we can make Boo jump through, we should look at the syntax.

2.3. Basic Boo syntax

Instead of explaining Boo’s syntax by just printing its formal Backus-Naur Form (BNF), we’ll look at a few sample programs. They should give you a feeling for the language.

Our first sample program will be, of course, Hello World. To make it a bit more interesting, we’ll use WinForms for it. Listing 2.4 shows the code.

Listing 2.4. Hello World using WinForms
import System.Windows.Forms

f = Form(Text: "Hello, boo!")
btn = Button(Text: "Click Me!", Dock: DockStyle.Fill)
btn.Click += do(sender, e): f.Close()
f.Controls.Add(btn)

Application.Run(f)

In this example, we start by importing the System.Windows.Forms namespace. This statement is similar to the using statement in C#, but in Boo this also instructs the compiler to load the System.Windows.Forms assembly if the namespace could not be found in the loaded assemblies.


Significant-whitespace versus whitespace-agnostic mode

If you’ve done any work with Python, you’re probably aware that one of the major gripes against the language is the significant-whitespace issue. Python uses the indentation of the code to define control blocks. This means that you don’t need curly braces or explicit block endings like end (in Ruby) or End If (in VB.NET).

The code looks like this:

if lives == 0:
print "Game over!"
game.Finish()
else:
lives--
print "${lives} more lives"

This is a major point of contention with Python, and because Boo uses the same convention, the same complaint has been raised against it as well. But having spent several years working with Boo, I can say that it rarely causes any problems.

“Rarely” isn’t the same as “never,” though. When I wrote Brail, a templating language using Boo, the whitespace issue was a major issue. I wanted to use the usual HTML formatting, but that messed up the formatting that Boo required.

Fortunately, Boo comes with a whitespace-agnostic mode, which you can utilize by flipping a compiler switch. When using whitespace-agnostic mode, the previous code would have to be written slightly differently:

if lives == 0:
print "Game over!"
game.Finish()
else:
lives--
print "${lives} more lives"
end

The only difference that you’ll notice is the end at the end of the statement. This is because Boo will now use the structure of the code to figure out where control blocks begin and end. Usually, this means that a block starts when a statement ends with a colon (:) and ends with an end keyword.

When writing DSLs, I tend to use the significant-whitespace mode, and only go to the whitespace-agnostic mode when I find that the whitespace causes issues.



Tip

When you aren’t sure how to do something in Boo, try doing what you would in C# (with the obvious syntax changes). In most cases, it will work, but note that it may not be the best way to do it in Boo.


Next, we create a Form and a Button. Boo supports property initialization at construction (C# 3.0 supports a similar notion, object initializers). Finally, we register the button Click event, add the button to the form, and start the application with the form as the main form.

Not bad for a quick Hello World application. If you wish, you can type the code in listing 2.4 directly into booish and experiment with the language.

Now, let’s try something a tad more complicated, shall we? Listing 2.5 creates an HTTP server that allows you to download files from a directory.

Listing 2.5. An HTTP server in Boo
import System.Net
import System.IO

if argv.Length != 2:
print "You must pass [prefix] [path] as parameters"
return

prefix = argv[0]
path = argv[1]

if not Directory.Exists(path):
print "Could not find ${path}"
return

listener = HttpListener()
listener.Prefixes.Add(prefix)
listener.Start()

while true:
context = listener.GetContext()
file = Path.GetFileName(context.Request.RawUrl)
fullPath = Path.Combine(path, file)
if File.Exists(fullPath):
context.Response.AddHeader("Content-Disposition",
"attachment; filename=${file}")
bytes = File.ReadAllBytes(fullPath)
context.Response.OutputStream.Write(bytes, 0, bytes.Length)
context.Response.OutputStream.Flush()
context.Response.Close()
else:
context.Response.StatusCode = 404
context.Response.Close()

In this code we have a bit of argument validation, and then we set up an HttpListener and start replying to requests. This isn’t the most interesting bit of code I have ever seen, but it does touch on a lot of different topics.

There are a few things that you should pay attention to in this example:

  • The argv parameter is a string array (defined by Boo) that holds the command-line parameters passed by the user.
  • The usage of HttpListener requires administrator privileges.
  • You can execute the code using the following command from the command line (assuming that you save the code as http_server.boo):
    path oooi http_server.boo boo_server . You can then go to http://localhost/boo_server/http_server.boo to download the http_server.boo file.


That annoying semicolon

One of the more annoying things when moving between languages is the semicolon. When I move between C# and VB.NET code, I keep putting semicolons at the end of VB.NET statements, and forgetting to end my C# code with semicolons.

Boo has optional semicolons, so you can put them in or not, as you wish. This makes temporarily dropping into the language a far smoother experience, because you don’t have to unlearn ingrained habits.


For anyone proficient in .NET, what we’re doing should be familiar. The language may be a bit different, but the objects and methods we call are all from the Base Class Library (BCL). This familiarity is welcome. We don’t need to learn everything from scratch.

Let’s look at one last example before we get back to talking about DSLs. Listing 2.6 shows a simple implementation of GREP.

Listing 2.6. A simple GREP implementation
import System
import System.IO
import System.Text.RegularExpressions

# Get all files matching a specific pattern from a directory
# and all its subdirectories
# Note: string* is a shorthand to IEnumerable<string>
def GetFiles(dir as string, pattern as string) as string*:
# Get all subdirectories
folders = Directory.GetDirectories(dir)
# For each subdirectory, recurse into that directory
# and yield all the files that were returned
for folder in folders:
for file in GetFiles(folder, pattern):
yield file
# Yield all the files that match the given pattern
for file in Directory.GetFiles(dir, pattern):
yield file

# Argument validation
if argv.Length != 2:
print "Usage: grep [pattern] [regex]"

filePattern = argv[0]
textPattern = Regex(argv[1])

# Get all the files matching the pattern in the current directory
for file in GetFiles(Environment.CurrentDirectory, filePattern):
# For each file, read all the lines
using sr = StreamReader(file):
while not sr.EndOfStream:
line = sr.ReadLine()
# If the line match the given pattern, print it
if textPattern.IsMatch(line):
print file, ":", line

In listing 2.6, GetFiles returns a type of string*, which is another Boo syntax shortcut. string* is the equivalent of IEnumerable of string. And more generally, T* is equivalent to IEunumerable of T, where T is any type. This code also uses yield to return results in a streaming fashion.

You can execute this code using the following command:

path	oooi grep.boo *.cs TextBox

This will search the current directory for all the C# files that make use of TextBox.


More Boo information

This chapter offers a high-level overview of Boo. If you’re new to the language, I recommend reading the appendixes to complete your knowledge of the language:

  • Appendix A contains a Boo tutorial for beginners, which can guide you through the initial steps in learning the language.
  • Appendix B contains a syntax reference comparing Boo with C#, which should help you to translate your C# knowledge to Boo.

In general, Boo should be fairly easy to grab and use if you’re already familiar with a programming language of some kind.


I hope that this gave you a feel for the Boo language. Now let’s continue exploring why Boo is a good language for writing DSLs.

2.4. Boo’s built-in language-oriented features

Let’s look at Boo’s language-oriented features, starting with the simplest and moving on to the more complicated ones, always with an eye to how we can use them for building DSLs. The first few examples involve no changes to the compiler. Boo comes with a lot of features to support DSLs.

2.4.1. String interpolation

String interpolation is syntactic sugar on top of string builders, nothing more. This means that you can put an expression like ${} inside a string, and anything inside the curly braces will be evaluated and appended to the string.

Consider this example:

name = "dear reader"
print "Hello ${name}"

This code will print “Hello dear reader”. It’s a direct translation of the following C# code (with no attempt to translate the semantics):

string name = "dear reader";
Console.WriteLine(
new StringBuilder()
.Append("Hello ")
.Append(name)
);

This is nice when you want to output text with variables, but without all the messy string concatenation.

Note that this works for static strings only, because it’s a transformation that happens at compile time. You can’t do something like the following and get the same experience:

str = "Hi ${" + PerferredNameFormat + "}"
print str.Evalute()

There is no way to take a string and evaluate it for string interpolation. If you need to dynamically change how you build strings at runtime, you’ll need to use StringBuilder directly.

2.4.2. Is, and, not, and or

Is, and, not, and or are keywords in Boo. The is keyword is the reference equality operator. And, not, and or have their usual meanings. By combining them, we can structure sentences that are valid Boo code while being very readable.

Here’s an example:

customer.CurrentPlan is null and customer.RegisteredAt > SixMonthsAgo

This can be read almost like English and demonstrates that you can compose readable statements using those keywords.

2.4.3. Optional parentheses

The ability to skip specifying parentheses on method calls might look like a small feature, but it has a significant effect on readability.

Compare this,

SuggestRegisterToClub("Preferred")

and this:

SuggestRegisterToClub "Preferred"

Not much of a change, I’ll admit, but the second version is easier to read. There is less syntactic noise for you to parse.

In fact, I would generally write it as follows, to make it even easier:

suggest_register_to_club "Preferred"

We’ll revisit the idea of reducing the syntactic noise later in this chapter—the less noise, the more clarity. This gets even more interesting with anonymous blocks, which are another way Boo helps us reduce syntactic noise.

2.4.4. Anonymous blocks

Anonymous blocks are a feature of Boo that allows you to use a more natural syntax for passing delegates to a method. The way it works is quite simple: if the last parameter of a method is a delegate, you can start a new code block and the compiler will treat the new code block as the body of the delegate that you pass to the method.

What this means is that instead of using this C# code,

List<int> ints = GetListOfIntegers();
ints.ForEach(i => Console.WriteLine(i) );

we can use this Boo code:

ints = GetListOfIntegers()
ints.ForEach do(i): print i

If we want to execute an action and don’t need parameters, we can do the same thing like this:

ints.ForEach:
print "foo"

Those are fairly silly examples, but you’ll use these techniques regularly when writing DSLs. Because the syntax flows more naturally, the DSL lets the user express intention more clearly and easily.

Suppose you wanted to perform some action when a new message arrives. You could write it like this:

msgBroker = MessageBroker()
msgBroker.WhenMessageArrives:
print "message arrived"

In this instance, it’s just another way to perform event registration, but I’m sure you can see the potential. It allows you to turn method calls into full-fledged keywords.

2.4.5. Statement modifiers

Statement modifiers are another tool for producing clearer code. By using them, you can turn several statements into a single sentence. You can use the following statement modifiers: if, unless, and while.

Consider this code:

if customer.IsPreferred:
ApplyDiscount(0.5)

We can make it more readable by using a statement modifier, like this:

ApplyDiscount(0.5) if customer.IsPreferred

The first example has a very code-like structure, whereas the second flows more naturally. Statement modifiers allow you to change where you put the condition: instead of keeping it as an independent statement, you can make it part of the statement it applies to. This is a small change, moving the condition around, but like most of the topics we’re discussing in this chapter, it can significantly enhance clarity when used properly.

In fact, we can make it even better by removing the parentheses and changing the method name:

apply_discount_of 0.5 if customer.IsPreferred

Some people feel that statement modifiers are harder to read, because they have to read to the end of the statement to figure out what is going on. For myself, I feel that this is a natural representation of the way we speak.

But this example points out another interesting usage pattern. Naming is different in code and natural language, and we can take advantage of that by using naming conventions.

2.4.6. Naming conventions

The CLR comes with clear guidelines about naming, and the BCL follows them closely. Names are in Pascal case for classes and members, and camelCase for parameters; abbreviations should be Pascal case unless they’re two letters long, in which case both letters are capitalized, and so on. After a while, this style becomes an ingrained habit. But now I’m going to ask you to forget all of that. We aren’t building an API to be consumed by other developers, who can ReadPascalCaseStatementsJustFine. We’re building a language to be read, full stop.

When it’s time to name your methods and classes, you should do so in the context of their usage, and their usage is in text, not in code. The previous example of changing ApplyDiscount to apply_discount_of is one example. It’s easier to read apply_discount_of than the more visually condensed version.


Naming conventions and holy wars

The choice of naming conventions can be a heated topic. Some people swear by Pascal case, others think that underscores are the pinnacle of clarity, and others are well aware that camelCase is the way the universe was written. And so on.

I won’t take sides in this argument. I’ll just say that I have found underscores to be useful in reducing the density of some of my DSLs. As usual, you need to make your decision based on the wide variety of factors in your own scenario.

I urge you to consider your naming conventions carefully when you build a DSL. Regardless of your final choice, it’s an important part of the way you structure your language.


This isn’t just a matter of using underscores versus Pascal case. It’s a matter of considering the method name in the context of the language. For instance, I might have several variants of a method name, depending on its usage, all forwarding to a single method. This can be done for clarity of intent in a particular usage, or as a way to specify additional intent.

Let’s look at an example where we’re publishing a message to various sources. We could have all of these methods call the same underlying method:

publish_message_to_group "administrators"

publish_message_to_owner

publish_message_to_user "joe"

If we were writing code, we’d probably use something like PublishMessage(destination), but the different method names provide additional clarity in different contexts. They allow us to be clearer about what we want to express.

Another way to provide multiple names for a single method is to extend the Boo compiler in such a way that it automatically translates from one naming convention to the other. The Boo compiler has rich support for that, and we’ll discuss it extensively in chapter 6.

It might be easier to provide a DSL facade over your application code, to isolate the language-oriented naming from the CodeOriented naming. Extending classes using extension methods and properties is one way of gaining clarity in the DSL while still keeping the core code clear of different naming styles. We’ll look at extension methods and properties in the next couple of sections.

2.4.7. Extension methods

Another way to employ naming conventions is to provide them externally, using extension methods. Classes in Boo are CLR classes, which means they follow the same laws that you’re familiar with from C#. You can’t add methods to a class or modify existing methods.[2] What you can do is to ask the compiler to pretend for you.

2 Well, you probably could do this, if you really wanted to, but I don’t recommend it. It would require modifying the compiler, and you’d still find that the CLR enforces some of those restrictions.

This Boo feature works like extension methods in C# 3.0, and it’s interoperable with the C# 3.0 version. Boo is a bit more powerful, adding support for extension properties as well as extension methods. Here’s a typical example of extending System.String:

class StringExtensions:
[Extension]
static def ToPascalCase(str as string):
return char.ToUpper(str[0]) + str.Substring(1)

The definition of ToPascalCase here creates an extension method, which means that we can now call ToPascalCase as if it were an instance method on System.String, rather than a static method on a different class (and assembly). Unlike C#, where we only need to reference the class’s namespace in the using block, in Boo we need to import the class itself. So in order to use the ToPascalCase extension method we need to write

import StringExtensions

"some string".ToPascalCase()

This is nice, but don’t go overboard with it. I don’t recommend writing extension methods to your own classes in regular code, but different rules apply to DSLs.

2.4.8. Extension properties

Extension properties are like extension methods, but they use properties.

For example, suppose we want to extend the IList interface to support the Length property. We could use the compiler support for extension properties to attach this property to everything that implements IList by using the following syntax:

class ListExtensions:
[Extension]
static Length[list as IList]:
get:
return list.Count

In general, extension properties can be used for the same reasons as extension methods: to get additional clarity without creating untidy code, and to create more expressive expressions. As with extension methods, you need to import the class that exposes the property before you can make use of the extension property. So, in order to use the Length extension property, we would write the following:

import System.Collections
import ListExtensions

a = ArrayList()
print a.Length

Extension properties are quite useful when we want to construct sentences. For example, we could extend the Int32 class so it knows about dates and times, making the following property chain valid:

2.minutes.from.now

This is done using extension properties and simple fluent interface, and it allows us to express complex values in a natural manner.

So what if, instead of extending a type by a single property or method, we could extend a type at runtime? Welcome to the world of duck typing ...

2.4.9. The IQuackFu interface

If it walks like a duck and it quacks like a duck, then it must be an IQuackFu. Other languages call it duck typing or method missing (or message not understood) and many dynamic languages support it. Because Boo is a statically typed language (unless you explicitly tell the compiler that you want late-bound semantics), and because method missing is such a nice concept to have, Boo includes the IQuackFu interface to introduce this capability.

The IQuackFu interface, shown in figure 2.1, gives you the option to handle dynamic dispatch on your own, instead of having to rely on the language semantics.

Figure 2.1. The IQuackFu interface is Boo’s version of method missing.

Duck typing basically means that you don’t care what the actual type of an object is. As long as it supports the operations you need (as long as it walks like a duck), you can treat it as a valid object (it is a duck).

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 listing 2.7.

Listing 2.7. The input XML structure
<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 generate 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?

Listing 2.8. Using the XML object to output
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.9. The XML object implementation—method missing over an XML document
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.10. Manually calling IQuackFu methods
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 runtime, 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 language 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 recommend 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.

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 application 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.136.97.64