Saturday, July 07, 2007

Notepad Oriented Programming

One of the shameless goals of Freya is to become a Notepad Oriented Programming Language: you must be able to write Freya applications with the Notepad and little more (Reflector, perhaps?). But that's a pretty hard goal when you're designing a language inspired by the Algol/Pascal lineage. It's not only that you must use those begin/end blocks instead of curly braces. If you must keep method declarations apart from their implementations, as in Delphi, then you'll have to type a lot... or lean on an editor that replicates the missing implementations by user request.
I think we have achieved the above stated goal. Right now, you can write Freya code that looks as compact and easy as C# code, and in some circumstances, the Freya variant may be even shorter than the C# equivalent. To illustrate this, let's take a look at C# and Freya operators. Let's say we're writing a Complex class in C#. Here you have two user defined operators on that class:
// C#
public static Complex operator+(Complex c1, Complex c2)
return new Complex(c1.Re + c2.Re, c1.Im + c2.Im);

public static Complex operator-(Complex c1, Complex c2)
return new Complex(c1.Re - c2.Re, c1.Im - c2.Im);
In a first attempt, those two operators would be translated to Freya this way:
static operator+(c1, c2: Complex): Complex;
Result := new Complex(
c1.Re + c2.Re, c1.Im + c2.Im);

static operator-(c1, c2: Complex): Complex;
Result := new Complex(
c1.Re - c2.Re, c1.Im - c2.Im);
There's nothing to be especially proud of in the above example: our fragment is longer in Freya than in C#. It's true that we have spared ourselves from some Delphi.NET eccentricities. For instance, we have written inline implementations, avoiding those nasty duplications imposed by the interface/implementation artificial split. We have also saved something in constructor calls: we use new as in C#, instead of calling some named constructor, as Delphi requires. It has nothing to do with saving two or three characters in each call (we're loosing that tiny advantage by using static instead of class, as in Delphi), but our syntax makes easier to translate existing code from C# to Freya. Last, but no least, we can use symbolic names for the operators, instead of Add and Subtract, as in Delphi or Chrome.
Expression based implementations will let us simplify the above listing:
operator+(c1, c2: Complex): Complex =>
new Complex(c1.Re + c2.Re, c1.Im + c2.Im);

operator-(c1, c2: Complex): Complex =>
new Complex(c1.Re - c2.Re, c1.Im - c2.Im);
We have deleted both begin/end blocks, and both assignations to Result. Since operators are always public and static in .NET, we have also dropped the static modifier. We now have code comparable to C# in length, and maybe even shorter. But we can keep shortening our example:
operator+(c1, c2: Complex) => new Complex(
c1.Re + c2.Re, c1.Im + c2.Im);

operator-(c1, c2: Complex) => new Complex(
c1.Re - c2.Re, c1.Im - c2.Im);
We are showing the last addition to Freya: return type inference for expression-based implementations. This is not a full featured type inference, as in functional languages or a modern language as Nemerle. The Freya compiler only allows the omission of the return type when it finds an expression based implementation, and when the implementing expression is an instantiation expression. We think that complex inferences are not a good thing, at least with a language as Freya, so we have added some inference... up to a sensible point.
We can use return type inference in yet another case, as this example shows:
operator/(c1, c2: Complex) =>
using r2 := c2.Re.Sqr + c2.Im.Sqr do
new Complex(
(+c1.Re * c2.Re + c1.Im * c2.Im) / r2,
(-c1.Re * c2.Im - c1.Im * c2.Re) / r2);
In this case, we have a common expression block containing a new expression, so we can safely deduce the return type before resolving the whole expression.
Of course, you won't be forced to write code in this style: if you want your code to look like good old Pascal, you still can write it that way... and I'm not being sarcastic. There's an important problem with compact code: how you get there. When you write programs by assembling small pieces into bigger ones, the result will contain extra code and glue that you probably won't need. The most frequent reason has to do with the fact that modular code, as you store it in your mental pattern library, must deal with a yet unknown context, so it probably has extra checks for handling extreme cases and such. When you adapt those code pieces for a given task, some special cases render improbable, and the corresponding guarding code can be deleted.
A second source of redundant coding is gluing. How do you compute the square root of the Zipperstein-Marmaduke Formula? First, you must evaluate the ZMF and then you'll have to find that pesky square root. In your first attempt, it's highly probable that the ZMF return value was stored in a local variable. There are two possibilities about the final code: either you can keep a separate line for dealing with ZMF, just to keeping what your code does, or you can merge both computations in a single expression. It's up to you to decide.
The consequence: compact code may be easier to read than to write... as the failure of functional programming languages to gain enough users has shown. Freya doesn't require you to write the shortest possible code, but that's still an option you have once you master the language.

... by the way, now we can write the Ray class as follows:
Ray = sealed class

Origin, Direction: Vector;

property Items[Time: Double] => new Vector(
Origin.X + Time * Direction.X,
Origin.Y + Time * Direction.Y,
Origin.Z + Time * Direction.Z);

Labels: ,


At 1:15 PM, Anonymous Anonymous said...

Can you give me some tips on doing subordinates in scripts?


Post a Comment

<< Home