I've recently been trying to make myself and my skills more "marketable", so I launched a three-pronged offensive:
- to start an opensource project and get it on github,
- to get better understanding of networking protocols in general, and HTTP in particular,
- to deepen my understanding of the .NET (and Mono) platforms.
The result of this so far is my most recent project, httplib1, which is a webserver library, built on top of the System.Net.HttpListener class. The github also contains a couple of executables built against the library—a server for general testing and development of ideas, and a server for what I hope will be a modern forum application.
While C# is the obvious choice of language for this kind of project, I actually decided to go with Nemerle instead—.NET being what it is, we still have access to any existing C# tools and libraries, we just need to 'translate' C# examples into Nemerle when reading the docs (which is actually pretty easy to do, given the comparison between the two on the Nemerle github wiki). The main reasons for choosing Nemerle are that it has a very strong influence from functional programming languages such as Standard ML and Scheme, while maintaining a somewhat familiar "C-family" syntax. But the primary attraction of Nemerle for me is its macro capabilities which I want to expand on a little more in this post.
Unlike C/C++, Nemerle's if statement actually functions as an expression, which means that code like the following is perfectly legitimate:
def value = if(condition) { 1 } else { 2 };
(As an aside, the else clause is mandatory—its omission causes a syntax error. The when keyword exists for conditional execution of code. See here for further discussion of this.)
Coming as I do from a C++ background, I found myself wanting to write the above code more succinctly, as follows:
def value = condition ? 1 : 2;
It turns out that the ternary operator isn't natively supported in Nemerle, but we can build one easily enough using a macro. We can actually make this macro as simple or as complex as we like, depending on the quality of our implementation. But, as Nemerle fully supports type inference (omitting manifest type information from the source code which the compiler can infer), we ultimately want to author a macro which supports it too.
The basic macro definition is a binary operator for ?, the outline of which is as follows:
macro @?(condition, predicate) { def type_builder = Macros.ImplicitCTX(); def expansion(condition, predicate, last_try) { def texpr = type_builder.TypeExpr(condition); /* ...expansion code here... */ } def result = expansion(condition, predicate, false); match(result) { | Some(r) => r | None() => { type_builder.DelayMacro( last_try => { expansion(condition, predicate, last_try) }) } } }
(The type_builder object is our interface to the Nemerle compiler at compile time: for the purposes of this post, we can use it to get type information for expressions we are evaluating, and to ask for the macro expansion to be postponed if the type information hasn't yet been inferred.)
The main body of the macro is defined in a local function expansion() which takes the arguments to the macro (condition and predicate) and a flag last_try which will be explained shortly. expansion() returns either Some(expression) or None() (the full return type is Nemerle.Core.option[Nemerle.Compiler.Parsetree.PExpr]—either a valid piece of AST, or nothing). In the case that we return some expression, we can simply use that expression as the macro's result; if None() is returned, the type_builder doesn't have enough type information at the time of macro expansion: we need to call the method type_builder.DelayMacro(), which is expecting a lambda expression of type bool -> option[PExpr]. In this case, we want to call our expansion() function again, and let the typer provide the last_try variable. (If last_try is true and we still haven't been able to expand the macro satisfactorily, then we will give up and call Message.Error() from within expansion(), causing a compile error.)
This basic outline should work for any Nemerle macro where delayed typing is needed—so we can now think about the contents of the expansion() function at the point marked /* ...expansion code here... */. The two arguments, condition and predicate represent the AST expressions on the left-hand and right-hand sides of the ? operator in the source code. We need to validate these expressions and emit our own AST as a result, or we need to cause a compile error if our validation fails.
Nemerle allows Scheme-like quasiquotation, to match and build syntax. The final result of our macro in the successful case will be this (as we'd expect, it's simply an if/else expression wrapped in a Some() constructor):
Some(<[ if($c) { $pass } else { $fail }; ]>)
The bracket markers <[ and ]> delineate the quasiquotation. Within it, $name allow us to insert (or 'splice') the contents of a variable. In the expression above, the variable c contains the result of matching condition, and pass and fail are derived from predicate. Without going into painstaking detail here, c is either simply condition itself if it's a bool expression, otherwise it is set to <[ $condition != null ]>. This allows us to write expressions such as the following (where o is an object reference), allowing use to omit the != null which would otherwise be required:
def result = o ? o.callMethod(): default_value;
This refinement is evoking the C++ idiom for testing the value of a pointer in a boolean context:
auto result = p ? p->callMethod(): default_value;
With regard to the predicate argument, we match it against <[ $pass : $fail ]>, which indicates we are expecting two expressions separated by a :, so we aren't building a true ternary operator; just a binary ? one which expects to find a : on its right-hand side. This isn't perfect (it doesn't handle nested ternaries as well as could be desired), but is probably as close as we can get without compromising our desired syntax, and we can always use parentheses to disambiguate if necessary, for example:
a ? (b ? 1 : 2) : (b ? 3 : 4);
(We can always argue that nested ternary operators is poor style, and should be discouraged anyway.)
The full source code is in my httplib project on github— it should be pretty straightforward to take this file and incorporate it into another codebase as-is, or with modifications for namespace. Some basic NUnit tests are also available here. (This code is under a 3-clause BSD license). One thing to bear in mind is that Nemerle macros need to be compiled into their own DLL, which the compiler references (e.g. something like ncc -r macros.dll -o out.exe source.n). For anyone seriously interested in Nemerle, there's a wealth of information on their github wiki, which provided all the information I needed to build this macro—the pages under "Macros extended course (Parts 1-4)" in particular go into a lot of detail on how macros work.
1 Again, I should draw attention to how bad I am at choosing names for projects.↩