A quick note: at some point when implementing the Mix runtime I had
occasion to write the Int
class. I think I've mentioned
abstract classes before; they act as bindings to .NET classes that exist
in the runtime, or in user supplied code (in case you want to use your regular
.NET code from Mix, and that code can't be automatically imported using
the import
statement because the types that you want associated
with your code are too complicated for Mix to infer).
Anywho I was writing this class and came across a decision I had made a while
ago that caused me to scratch my head. In Mix operators are just method calls,
which gives an easy to understand form of
operator overloading,
whereby an operator invocation is just a method invocation on one of the arguments.
Which argument is operator dependent, but I think almost all of them
use the left argument, passing the right; of course, single argument operators
like ++
are easy.
The decision that I made was to make certain operators "real" operators, not
just syntactic sugar. For instance, I made operators ++
and
--
real operators, and didn't just expand them from i++;
to i = i + 1;
. Seems cleaner, actually, to not treat it as
sugar, so what's the big deal?
Well, in Mix all expressions are objects -- we've talked about functions being objects, methods being objects, and so on. We also have integers as objects, booleans as objects, and so on (note: I write "and so on" because I don't like the strangeness of punctuating an etc. -- I like putting my punctuation on the outside of parens, it just feels right). Another note: for now I want to ignore the performance implications of this choice, and focus on a clean language design. Another other note: classes themselves aren't objects, unfortunately; maybe in Mix version 2.
So integers are just instances of class Int
, and we all love
to increment our integers using i++;
. So how do we write the
.NET class implementing the Mix class Int
?
First, let's look at the abtract class itself:
abstract class Int { public ToInt(){ return null as Int; } public ToString() { return null as String; } // More conversion operators... public operator+(i) { i.ToInt() as Int; return null as Int; } // More arithmetic operators... public operator++() { return null as Int; } // And anything else... }
Let's take this in a few bites, my hungry friends. First, the syntax
Given this description, you should be able to understand the definition of
So, in
Finally we come to the real issue: how should we implement
Well, that's craziness, because integers are almost always in everyone's
minds, immutable. In the following, we pretty much all expect
And of course with the above definition it isn't, not even a little. So,
leave your integers immutable! But, how can we write the code, allowing for
One option is to say that when a class implements
Maybe someone, somewhere has a thought or three?
term as Type
does two things: at compile time, it just checks that
the term
has only the given Type
, and then returns
a typeset containing just Type
. At runtime, it does nothing,
returning term
; in an abstract class, it really does nothing,
as class never has any code generated for it. The point of the
null as Type
idiom is to avoid writing
new Int()
, because
it's not as obvious that it's just for compile time.
operator+
; it checks that its argument is convertible to
Int
, and then returns an Int
; the code to actually
perform the addition is found in the .NET class implementing Int
:
public class Int
{
public int NativeInt;
// Lots of methods...
public operator_add(object other)
{
int i = Mix.Call(other, "ToInt").NativeInt;
return new Int(NativeInt + i);
}
public operator_incr()
{
// What to do?
}
}
operator_add
we invoke ToInt
on the passed
object and then assume that it worked, and that the result is an Int
.
We can safely assume these, because the Mix compiler verified that all
code is well-typed. This lets us get the native .NET integer, add it to our
own, and return a new wrapper with the sum of these two numbers.
operator_incr
.
Turns out the obvious way to implement leads to really crazy use cases! Let's
say that the body is as follows:
public operator_incr()
{
int original = NativeInt;
NativeInt++;
return new Int(original);
}
b
to be 42
:
var a = 42;
var b = a;
a++;
operator++
to be a real live operator? I'm not sure, but I
certainly couldn't find a clean way (nor for operator+=
and company).
operator++
we
call it, otherwise we interpret it in the "syntactic sugar style". The nice
bit about this is that it also works for operator+=
and friends, so
we can, for instance, write a list that allows you to append elements to it using
list += "new list item!"
. The problem is that it complicates both
the mental model of the code, and also the implementation of the back end.
No comments:
Post a Comment