Critical Development

Language design, framework development, UI design, robotics and more.

The Archetype Language (Part 3)

Posted by Dan Vanderboom on April 27, 2010

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:

Language Design: Complexity, Extensibility, and Intention

Reimagining the IDE

Better Tool Support for .NET

Exception Handling

The try keyword can be used within any code block.

ProcessItem void (Item Item)

{

try

{

// throw a runtime exception

}

catch (ex Exception)

{

// handle exception

}

finally

{

// cleanup

}

}

Additionally, every function can specify its own inline catch and/or finally blocks like this:

 

ProcessItem void (Item Item)

{

// throw a runtime exception

}

catch (ex Exception)

{

// handle exception

}

finally

{

     // cleanup

}

You’ll see this pattern appear in other constructs in Archetype (such as async blocks).

 

A try block can exist with one or more catch clauses only, a finally clause only, or both. If both are included, the catch clauses must come first. This is true whether they’re defined as part of the function (example 0) or have an explicit try clause (example 1).  Since curly braces are optional for single statement code blocks, we can write this:

try Work();

catch (x Exception) Log(x);

finally Finish();

The exception type can be specified by itself (without a name), and the default variable "ex" will be used. If a catch block doesn’t specify an exception type, Exception is presumed.

try Work();

catch (ArgumentException) Log(ex);

catch (NullReferenceException) Log(ex);

catch Log(ex);

finally Finish();

The order of catch blocks must be from most derived to most base (Exception itself must always be last, if present). Incorrect ordering will result in a compiler error.

Catch and finally blocks are scoped as nested within the try block. This enables catch and finally blocks to reference identifiers defined in the try block.

try

{

var answer = 42;

}

catch

{

// valid reference to answer

var a = answer;

}

As with C#, the throw keyword can be used with an Exception variable to wrap and rethrow a caught exception, or throw can be used to in a statement by itself to rethrow the original exception.

Namespace Imports

In Part 2, we saw the first of the import keyword to import namespaces.

import System;

 

Start int ()

Console.WriteLine("Hello world!");

Like Nemerle, we can also apply the import keyword with classes to access static members without specifying the class name.

import System.Console;

 

Start void ()

WriteLine("Hello world!");

Another option is to import a namespace or class into a nested function or class scope.

Start void ()

{

import System;

Console.WriteLine("Starting up…");

}

 

Employee object

{

import System;

 

Work void ()

{

Console.WriteLine("Working hard!");

}

}

Similar to the with keyword in Pascal, import can be used to import a namespace or class for a specific code block. This limits a namespace or class import to a limited section of a function.

Start void ()

{

System.Console.WriteLine("The following import doesn’t apply here.");

 

import System.Console

{

WriteLine("hello");

WriteLine("goodbye");

}

}

One final thing you can do with import is to specify a namespace alias.

Start void ()

{

import sys = System;

sys.Console.WriteLine("Example of a namespace alias");

}

Aliases

The section on namespace imports above introduced namespace aliasing.  In this section, we’ll see how to use the alias keyword to provide additional identifiers to classes, functions, properties, and fields.

The class alias is similar to the import class pattern, except that a new identifier is introduced and must be used to reference its members.  (With import, that class’s static members are implicitly accessible.)

DemonstrateAlias void ()

{

alias kid = Geneology.Child;

var Josa = new kid;

Josa.FirstName = "Josa";

Josa.Age = 4;

}

In addition to the alias statement, I’m introducing object instantiation syntax in Nemerle.  The constructor of the Geneology.Child class is being called (via its alias, kid) with the new keyword but no parentheses, which are optional when calling a parameterless constructor.

The var keyword is also new here.  It is similar to the var keyword in C#, except that it’s required for local variable definitions.

This example demonstrates alias used for a local variable.  The syntax is identical for class fields and properties, except that those aliases can be specified at the class level as well as within functions.

DemonstrateAlias void ()

{

var SocialSecurityNumber = "123-456-7890";

alias SSN = SocialSecurityNumber;

System.Console.WriteLine(SSN);

}

This last alias example shows how it can be applied to functions.

DoWork void () { … }

Test void ()

{

alias work = DoWork;

work();

}

Control Flow

We’ve already discussed exception handling, which is a very fundamental kind of control flow structure.  In this section, we’ll explore several other constructs that are familiar to every programmer.

In Programming Language Pragmatics, the author (Michael L. Scott) enumerates six essential types of control flow: sequencing, iteration, selection, exception handling, recursion, and concurrency.  We’ll cover most of them in this article.

Sequencing is merely the scheduling of one statement to be executed after another.  This is the standard model of interpreting source code statements in a code block, so there’s not much more to say about it.

Iteration

Iteration is much more interesting.  In Archetype, there are four iteration constructs.

Loop

The first is the incredibly versatile loop.  It can take a simple integer expression to loop a specific number of times.  It can alternatively take an expression that introduces a variable, a range of values, and an optional skip value (similar to the for keyword in Visual Basic).  Finally, it can act like the foreach keyword in C# and iterate over IEnumerable and IEnumerable<T> collections such as streams, lists, etc.

// loop 10 times

loop (10)

{

}

 

// i starts at 3, increments by 1, until it reaches 9

loop (var i in 3..9)

{

}

 

// i starts at 11, decrements by 2, until it reaches (or passes) 1

loop (var i in 11..1 skip 2)

}

 

// define cust, then loop through and reference each object in an IEnumerable

loop (var cust in Customers)

{

}

This replaces the archaic syntax of the for loop in C# and older C-style languages, and provides a construct which is much easier to read and write.  Each of the integer constants in the examples above can be replaced with expressions (variables, function calls returning integers, etc).

When writing a loop, it’s often necessary to skip the remainder of the current iteration and continue with the next one.  In C#, the ambiguous-sounding continue keyword is used.  I remember seeing this for the first time and thinking that it meant to continue executing after the loop, which wasn’t the case.  So in Archetype, I’m ressurrecting the venerable old keyword next, as in “go to the next iteration in this loop”.  To break out of a loop altogether and continue executing after the loop, the break is used.

// define the int i and loop from 0 to 9

loop (var i in 0..9)

{

// do some work

 

if (DoneWithThisIteration)

next;

 

if (DoneWithLoop)

break;

 

// continue with more work

}

Fork-Join

A potent addition to the loop construct is the fork-join pattern.  The fork and join words are actually defined in a library, not in the language itself.  In a later article, you will see how to create patterns like this.

// fork out a bunch of parallel tasks and join when all are done

fork (var cust in Customers)

{

// this code is encapsulated in a task in the TPL

// and scheduled for execution

}

join (tasks)

{

// this code block is executed when all of the tasks

// are either completed or canceled

}

The join clause’s parameter, named tasks in the example, is a reference to a list of Task Parallel Library (TPL) tasks.  This is a handy way to execute code in parallel without having to restructure your code (similar to the Parallel.ForEach method in the TPL).  Most of the difficulties of concurrent programming are matters of coordination, however, and so they are often best handled by parallel libraries such as the TPL or the Concurrency & Coordination Runtime (CCR).

While and Until

The next example is the familiar while loop.  It matches a condition at the beginning of each iteration and only executes the following code block if the expression provided is true.

// repeat while condition is true

while (a == 10)

{

}

The until loop works similarly, but tests its condition after the following code.

// repeat until the condition is true

until (str.Length == 0)

{

}

Although it makes sense in C# that the until clause should appear at the end, where it’s placed, the reality is that in C# the syntax is awkward: I don’t know whether to keep my curly braces aligned and put the do and until on their own lines so they don’t crowd the embedded code block, or what.  With the naming difference in Archetype, and with the debugger’s help in stepping through in the correct way, I’m betting this feature will not only be easy to grasp, but hopefully will be seen to clean up certain coding situations and help make looping syntax more structurally consistent.

The break and next keywords apply to while and until loops as it does for loop constructs (see the Loop section above).

Asynchronous Programming

It’s becoming more common now to make asynchronous calls to web services and other long-running processes that we don’t want our code to sit around waiting for.  In Silverlight, for example, the only network communication options we have are asynchronous.  But the Asynchronous Programming Model (APM) has a way of confusing and tripping up developers as they try to wrap their heads around it.

Archetype introduces a few language constructs to make asynchronous programming easy.

Calling Methods and Delegates Asynchronously

The async keyword allows you to call any method or delegate asynchronously with the same syntax.

GetData string (Index int)

{

return (Index + 1).ToString();

}

 

async GetData(42)

{

var result = value;

}

All of the details of dealing with AsyncCallback and IAsyncResult are abstracted away.  The value keyword represents the return value of the asynchronously-called method (if applicable).  The first access of value may result in an exception in the event that the target method failed when it ran.  This can be caught with a standard try-catch block, or the following syntax can be used.

async DoWork()

{

// success

}

catch (ex ArgumentException)

{

// failure

}

finally

{

// final logic

}

All of the standard rules apply regarding the syntax of catch and finally blocks (see the Exception Handling section above in this article).

As another reminder of the optional curly braces for single statements, here is a short and simple async call example.

async DoWork()

NextStep()

catch HandleError(ex)

finally Cleanup();

The ability to specify your intent to call methods and delegates asynchronously without mucking around in the implementation details should go a long way to making developers more productive in high-latency scenarios.  In a final example of async, I’ll demonstrate how we can still obtain access to the IAsyncResult variable that is returned by the APM, which is useful if you need to occasionally check if it’s completed.

var ar = async DoWork()

NextStep()

catch HandleError(ex)

finally Cleanup();

Messages

Messages offer an alternative to delegates.  As useful and simple as delegates are, the problem with them is that they pass along not only data, but also execution control.  Often what we need, in particular for applications that must take advantage of parallel execution, is a way to pass along data without giving up control over execution.  This is usually done by pushing a message onto a queue which can be picked up at the receiver’s convenience without holding up the sender.

In Archetype, messages fulfill this need.  Like delegates, they can be named or anonymous.  Here is an example of each.

message EmptySignal();

SomeAgent agent

{

// using a named message

Started out EmptySignal;

 

// using an anonymous message

Completed out message(string);

}

Unlike their delegate counterparts, messages don’t have return values.  Return values only make sense when you make a synchronous call and give up execution control.  Though we can get around this with the async construct and the Asynchronous Programming Model, this is something of a hack.

In the example above, the anonymous message provides the keyword message in place of a return type.  In both cases, the out keyword is used to indicate that it is an outgoing message.  As you might expect, there is an in keyword to indicate incoming messages.

Messages are one way communications.  If you need a response, you need to specify a corresponding incoming message.  This applies to the communication of error information as well.  If you’re interested in learning more about asynchronous message-based communication, you can refer to my article on the subject.

The out messages act like delegates and can be called like functions, and you can think of in messages as event handlers, although they can also be invoked like methods within the agent.

I am in the process of evaluating Axum and other agent-based languages.  I’m particularly interested in the way Axum defines protocol contracts, which are compile-time checks that message A is responded to by message B, and so on.  It’s likely that this type of constraint will be implemented using the language extensibility capabilities of Archetype, and that it will be deferred until I understand the issues and challenges better.  Until that time, I believe that having the option to define messages as first-class members will provide developers with a greatly-needed tool for safer concurrency programming.  It will make the execution control model explicit and obvious at member definition instead of being bolted-on later as a set of imperative instructions in an often-misunderstood corner of the .NET Framework.

Another avenue I’m exploring is a syntax for exposing messages as WCF endpoints.  Stay tuned for more information on this subject.

Next Steps

We covered a lot of ground in this article.  In these first three articles, many of Archetype’s most fundamental and important constructs have been explained and demonstrated.  In the next article, I’ll introduce Archetype’s capabilities and syntax for conditional selection (if), pattern matching (including a much-needed replacement for the switch statement), and the relationship between traits and classes.

There’s a lot more to see, but since this article turned out to be so large, I’ll stop here with promises about the next one.

[Part 4 of this series can be found here.]

Advertisements

4 Responses to “The Archetype Language (Part 3)”

  1. Mark said

    Dan,
    I am very interested in seeing where you are going with this. As you said this does not seem to be a language that we NEED, but rather want.

    One thought that occurs to me however, is that instead of hard-coding the syntax in to the language itself with particular constructs and keywords, why not build on more foundational primitives, like macros that can transform higher level ‘syntatic sugar’ into AST’s for processing. That might let the language evolve more rapidly.

    What are your thoughts?

    • Dan Vanderboom said

      Mark, the use of macros (or other kinds of language extension) are exactly what I had in mind. I want to build as little as possible into the language itself, and then provide extensions to do the rest. I’m still working out the details of language extension, though I have some pretty good ideas. I’ll be exploring those ideas in one of the next few articles.

      • Mark said

        Dan,
        That’s great to hear. Perhaps I got the wrong impression because you were talking about having keywords for things like “bindable”, “using”, try..catch..finally, etc when if the language supports powerful “macro” capabilities, in the lisp sense of the word, not C, then all of those things can be macros

    • Dan Vanderboom said

      Mark, the use of macros (or other language extensibility mechanisms) is exactly what I had in mind. At this point, I’m still working out the details. I’ve looked at Lisp and Nemerle macros, and script in F#. I also have some ideas of my own. But the basic idea is to keep the number of primitives (keywords, operators) to a bare minimum, only enough to enable all the other constructs that I want. I’ll be exploring these ideas in one of the next few articles.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: