Sunday, September 07, 2008

Enter Lyra

Stringed instruments, get it?Let me state this loud and clear: I'm proud of Freya and the Freya compiler. I think my goals, or almost all of them, has been achieved: there's a working implementation of a powerful general programming language, running on a powerful interesting framework. What's more: I have great plans for Freya, including the addition of some interesting features not found in similar languages.
There's a catch, however: Freya is a complex language, requiring a complex implementation. And I'm virtually alone, by my own decision, developing the language, the compiler and a simple IDE for testing... and that shows. For instance: almost all relevant features from C# 2.0 has already been included and implemented in Freya, and we already have some features from C# 3.0, as extension methods, and object and collection initializers. Nevertheless, there are some equally important features still missing, such as anonymous methods and complex type inference for lambdas (type inference for generic method calls, however, is already working). And the worst part is that we'll need to rewrite the type attribution from the compiler in order to accommodate the powerful inference engine required for these enhancements.

So enter Lyra (pronounced as lie-rah). Lyra is a fully developed and independent programming language derived from Freya. Its syntax is almost identical to Freya's, but we have introduced some simplifications, in order to make it easier to understand and implement. These are some of the differences:
  1. Implementation in Lyra takes place along with declaration. No more separated implementation for sections, for including executable content.
  2. Property and event implementations have changed accordingly. The new implementation syntax is very compact and easy to understand, and uses four new special "keywords": get:, set:, add: and remove:. Semicolons are an integral part of the corresponding tokens, and they avoid both those dirty contextual keywords and disturbing valid identifiers in existing code.
  3. Interface delegation is supported, but it has moved to the implementing member declaration.
  4. Constructor calls now requires parentheses, except when followed by a list initializer.
  5. In compensation, constructor calls can now start an object path.
  6. Local variable declarations have been moved to the statement list, as declaration statements. This trick avoids some syntactic anomalies while defining properties and events.
Let's see an example showing how to write properties in Lyra:
property Items[Index: Integer]: Boolean;
// Compound assignments are not expressions.
Index -= Min;
// then is optional before a keyword.
if Index < 0 or Index > High
raise new ArgumentException("Index");
// Symbolic and literal operators are both allowed.
Result := (Bits[Index div 32] &
(1 << (Index mod 32))) <> 0
Index -= Min;
if Index < 0 or Index > High
raise new ArgumentException("Index");
if Value then
Bits[Index div 32] |= 1 << (Index mod 32)
Bits[Index div 32] &= ~(1 << (Index mod 32))
Of course, we can still use most of the Freya tricks, such as expression-based methods and properties (and operators, and iterators), the abbreviated syntax for constructor chains, implicitly implemented properties (I think Freya/Lyra's trick is better than C#'s). Assertions are fully supported, even when declared in an interface type. Numeric types are still implicitly extended with static members from the Math class. Lyra, in other words, is as powerful as Freya... but probably more elegant, too.

In the next months, all our research and implementation effort will concentrate on Lyra. These are some of our planned tasks:
  • Anonymous methods and lambdas are still missing. Probably, I'll implement only one of the syntactic varieties, but I still have some troubles with the syntax: C#'s lambdas, when implemented verbatim, don't make a valid LALR(1) grammar.
  • I still believe in the usefulness of access sentinels: wrapping code that would be inserted by the compiler around methods that access some class members. This is a safe form of Aspect Oriented Programming (and no, properties don't do the same trick!)
  • Lyra will feature mixins declared with interfaces. You'll be able to add common implementation methods to interfaces, as in this example:
IStack = interface[X]
property Count: Integer; readonly;
method Push(Value: X);
method Pop: X;

// These are the new "mixins":

property IsEmpty: Boolean =>
Count = 0;

method Push(Values: IEnumerable[X]);
for v in Values do Push(v)

method Clear;
while Count > 0 do Pop
  • It's some kind of funny that extension methods (that's what mixins are, at the end of the day), access sentinels and assertions are related by their implementations. For instance, pre and postconditions are implemented in Freya as extension methods. Class invariants' implementation, on the other hand, uses a similar approach as data sentinels to avoid firing in internal call boundaries.
  • I must acknowledge that most assertion implementations I know, beyond Eiffel, are shallow, incomplete or plainly wrong. Most of them don't handle inheritance and don't support assertion on interface types. Assertions in Freya addressed these problems, and Lyra will continue enhancing assertions support.
  • I have to complete the "Intellisense" compiler. This is a second (lighter) compiler that must take care of class navigation and code completion. I can't use the main compiler: it performs some code generation tasks very early in the compiling process.
  • Of course, Visual Studio integration is still an important item in the shopping list, just as CodeDOM support is.
  • Compiling must be moved to an isolated application domain, in order to avoid problems when unloading compiled assemblies.
  • Dynamic methods and "dynamic programming" support, you said? Huh, I hate the very concept! But you were kidding, weren't you?
Let me insist, anyway: Freya development hasn't been abandoned. As soon as Lyra moves to the next level, we'll update Freya in order to keep the pace.

Labels: ,

Mean and lean

Wouldn't be a good thing if you were allowed to define functions like these in C#?
public long fact(int n) =>
n <= 1 ? 1 : n * fact(n - 1);
operator+(Complex c1, Complex c2) =>
new Complex(c1.Re + c2.Re, c1.Im + c2.Im);
Actually, you are allowed even riskier tricks when dealing with inline lambdas. The uniformity principle states that, at least, some of these tricks should be accepted while defining regular functions. And that's what Freya does.
By the way, there are no omissions in the operator definition... by Freya standards, of course. Operators are always public and static, so why bother the user with stating this each and every time. On the other hand, the return type has been omitted: it can be statically deduced from the defining expression. Freya has precise rules for these static inferences: you can omit the return type when the expression is a constructor call, a typecast or, recursively, when it's a common expression block wrapping an expression with static inferable return type or a conditional with identical inferred types in both of its branches.

Labels: , ,