Overview
This is part of a continuing series of articles about a new .NET language under development called Archetype. Archetype is a C-style (curly brace) functional, object-oriented (class-based), metaprogramming-capable language with features and syntax borrowed from many languages, as well as some new constructs. A major design goal is to succinctly and elegantly implement common patterns that normally require a lot of boilerplate code which can be difficult, error-prone, or just plain onerous to write.
You can follow the news and progress on the Archetype compiler on twitter @archetypelang.
Links to the individual articles:
Part 1 – Properties and fields, function syntax, the me keyword
Part 2 – Start function, named and anonymous delegates, delegate duck typing, bindable properties, composite bindings, binding expressions, namespace imports, string concatenation
Part 3 – Exception handling, local variable definition, namespace imports, aliases, iteration (loop, fork-join, while, unless), calling functions and delegates asynchronously, messages
Part 4 – Conditional selection (if), pattern matching, regular expression literals, agents, classes and traits
Part 5 – Type extensions, custom control structures
Part 6 – If expressions, enumerations, nullable types, tuples, streams, list comprehensions, subrange types, type constraint expressions
Part 7 – Semantic density, operator overloading, custom operators
Part 8 – Constructors, declarative Archetype: the initializer body
Part 9 – Params & fluent syntax, safe navigation operator, null coalescing operators
Conceptual articles about language design and development tools:
Params & Fluent Syntax
C# has a parameter modifier called params that allows you to supply additional function arguments to populate a single array parameter.
void Display(params string[] Names)
{
// …
}
Without the params modifier, we’d have to call it like this:
Display(new string[] { "Dan", "Josa", "Sarah" });
Because params is declared, we can do this instead:
Display("Dan", "Josa", "Sarah");
If there’s one thing you can take away from Archetype’s design, it’s that syntactic sugar is everything. After examining my own procedural animation library (Animate.NET) to see how it could be used best in Archetype, I came to the conclusion that these params parameters can be substantial. When they are, they create syntactic unpleasantries, especially when nested structures are involved.
Consider the following C# example.
var anim =
Animate.Wait(0.2.seconds(),
RedChip.MoveBy(0.4.seconds(), -40, 0),
RedChip.FadeIn(0.2.seconds()),
BlackChip.MoveBy(0.4.seconds(), 0, 40),
BlackChip.FadeOut(0.4.seconds())
)
.WhenComplete(a =>
{
MainStage.Children.Remove(RedChip);
MainStage.Children.Remove(BlackChip);
})
.Begin();
First, a quick explanation of the code. Animate is a static class, and the Wait function returns an object called GroupAnimation that inherits from Animation. After a 0.2 second wait, the following params list of Animation objects will execute. RedChip and BlackChip are FrameworkElements (Silverlight/WPF objects), and animation commands such as MoveBy and FadeOut are extension methods on FrameworkElement. Each of these animation commands returns an Animation-derived object. The seconds() extension method on int and float types convert to TimeSpan objects.
The ultimate goal of this first Wait section of code is to define a set of animations—nested sets are possible, which form a tree of animations. These trees can get more complicated than this, but we’ll keep the example simple for now.
Now for the criticism. Look at the matching parentheses of the Wait function. The normal TimeSpan parameter is listed as an equal along with the Animation parameter list, and what is being used as a complex, nested structure is holding up the closing parenthesis and dragging it down to the end of the entire list. If only there were a cleaner way of treating this nested structure like constructor initializers (see Part 8). These correspond, in terms of visual layout, to the attributes and the child elements of an XML node.
What else is wrong with this picture? The .WhenComplete and .Begin functions are being invoked on the result of the previous expression. It’s characteristic of fluent-style APIs to define functions (or extension methods) that operate on the result of the previous operation so they can be strung together into sentence-like patterns. The dot before both WhenComplete and Begin look odd when appearing on lines by themselves, and the lambda expression would be better promoted to a proper code block.
Finally, it’s unfortunate that in declaring a new local variable, we have to indent the whole animation block this way. Here’s what the same code looks like in Archetype:
Animate.Wait (0.2 seconds) -> anim
{
RedChip.MoveBy(0.4 seconds, -40, 0),
RedChip.FadeIn(0.2 seconds),
BlackChip.MoveBy(0.4 seconds, 0, 40),
BlackChip.FadeOut(0.4 seconds)
}
WhenComplete (a)
{
MainStage.Children.Remove(RedChip),
MainStage.Children.Remove(BlackChip)
}
Begin();
This is more like it. Notice the declarative assignment (declaration + assignment) with –> anim on the first line, and the way the parentheses can be closed after the TimeSpan object (see Part 7 on custom operators for an explanation of the syntax “0.2 seconds”). There’s no more need to indent the whole structure to make it line up nicely in an assignment. The following initializer code block (in curly braces) supplies Animation object values to the params parameter in the Wait function, and the WhenComplete and Begin functions don’t require a leading dot to operate on the previous expression (Intellisense would reflect these options).
The Archetype code is much cleaner. It’s easier to see where groups of constructs begin and end, enabling fluent-style APIs with arbitrarily-complicated nested structures to be easily constructed. Let’s take a look at one more example with a more deeply nested structure:
Animate.Group –> anim
{
RedChip.MoveBy(0.4 seconds, -40, 0),
RedChip.FadeIn(0.2 seconds),
BlackChip.MoveBy(0.4 seconds, 0, 40),
BlackChip.FadeOut(0.4 seconds),
Animate.Wait (0.4 seconds)
{
Animate.CrossFade(1.5 seconds, RedChip, BlackChip),
BlackChip.MoveTo(0.2 seconds, 20, 150)
}
}
Here, a GroupAnimation is defined that contains, as one of its child Animations, another GroupAnimation (created with the Wait function). The animation isn’t started in this case, so anim.Begin() can be called later, or anim could be composed into a larger animation somewhere. A peek at the function headers for Group and Wait functions should make the ease and power of this design clear.
static Animate object
{
// a stream is the only parameter
Group GroupAnimation (Animations Animation* params)
{
}
// a stream is the last parameter, so [ list ] syntax can still be used
Wait GroupAnimation (WaitTime TimeSpan, Animations Animation* params)
{
}
}
Because the class is static, individual members are assumed to be static as well.
The easiest way to support this would be to allow this initializer block to be used with a params parameter that’s declared last.
Null Coalescing Operators
The null coalescing operator in C# allows you to compare a value to null, and to supply a default value to use in its place. This is handy in scenarios like this:
var location = (cust.Address.City ?? "Unknown") + ", " + (cust.Address.State ?? "Unknown");
Chris Eargle makes a good point in his article suggesting a “null coalescing assignment operator” when making assignments such as:
cust.Address.City = cust.Address.City ?? "Unknown";
There should be a way to eliminate this redundancy. By combining null coalescing with assignment, we can do this:
cust.Address.City ??= "Unknown";
Groovy’s Elvis Operator serves a similar role, but operates on a value of false in addition to null.
Safe Navigation Operator
There are many situations where we find ourselves needing to check the value of a deeply nested member, but if we access it directly without first checking whether each part of the path is null, we get a NullReferenceException.
var city = cust.Address.City;
If either cust or Address are null, an exception will be thrown. To get around this problem, we have to do something like this in C#:
string city = null;
if (cust != null && cust.Address != null)
city = cust.Address.City;
The && operator is short-circuiting, which means that if the first boolean expression evaluates to false, the rest of the expression—which would produce a NullReferenceException—never gets executed. As tedious as this is, without short circuiting operators, our error-prevention code would be even longer.
Jeff Handley wrote a clever safe navigation operator of sorts for C#, using an extension method called _ that takes a delegate (supplied as a lambda). You can find that code here. In his code, he does return a null value when the path short circuits. As you can see, however, the limitations of C# cause this simple example to get confusing quickly, which you can see if we make City a non-primitive object as well:
var city = cust._(c => c.Address._(a => a.City.Name));
Groovy implements a Safe Navigation Operator in the language itself, which is cleaner:
var city = cust?.Address.City;
This is equivalent to the more verbose code above. Archetype takes a similar approach:
var city = cust..Address.City;
Because of the .. operator in this member access expression, the type of the city variable is Option<string> (more on Option types). If the path leading up to City is invalid (because Address is null), the value of city will be None. This works the same as Nullable<T>, except that None means “doesn’t have a value; not even null”.
I like to think of None as the “mu constant”. What is mu? It’s the Japanese word that variously means “not”, “doesn’t exist”, etc., and is illustrated by the well-known Zen Buddhist koan:
A monk asked Zhaozhou Congshen, a Chinese Zen master (known as Jōshū in Japanese), "Has a dog Buddha-nature or not?" Zhaozhou answered, "Wú" (in Japanese, Mu)
—The Gateless Gate, koan 1, translation by Robert Aitken
Yasutani Haku’un of the Sanbo Kyodan maintained that "the koan is not about whether a dog does or does not have a Buddha-nature because everything is Buddha-nature, and either a positive or negative answer is absurd because there is no particular thing called Buddha-nature.
In other words, Mu has often been used to mean “I disagree with the presuppositions of the question.”
There are a few basic patterns around options, nullable objects, and safe navigation that occur frequently, so I’ll outline them here with examples:
// if Address is null, this evaluates to false
if (cust..Address.City == "Milwaukee")
WorkHarder();
// if City is None because Address is null, set to "Address Missing"; otherwise, get the city text
var city = cust..Address.City ?! "Address Missing";
// if City is Some<string> and City == null, set to empty string
var city = cust..Address.City ?? string.Empty;
// if Address is null (City is None), set to "Address Not Found";
// but if City == null, set to empty string
var city = cust..Address.City ?! "Address Not Found" ?? string.Empty;
// if Address points to an object, leave it alone; otherwise, create a new object
cust.Address ??= new Address(City="Milwaukee");
// an assertion
cust..Address ?! new Exception("Address missing");
// set the city if possible, throw a specific exception if not
var city = cust..Address.City ?! new Exception("Address missing");
Summary
By now it should be obvious that Archetype aims to liberate the developer from the constraints and inefficiencies of ordinary programming languages. It is designed with modern practices in mind such as fluent-style development and declarative object graph construction.
This article wraps up the material started in Part 8 on declarative programming in Archetype. In addition, I introduced the safe navigation and null coelescing operators. These are simple but powerful language elements for cleanly and succinctly specifying common idioms that come up in daily coding.