One of the key things we've talked about already is the desire to provide arbitrarily named infix functions. Operator overloading on symbols leads to weird re-definition of behavior, yet entities (and games in general) want a bit of those operations that can work between types yet not necessarily own either. Infix notation makes it a lot nicer. So we'd end up with something like:
(Ogre)smash(Knight);
Which is nice, readable and concise. Its definition was a little awkward, but doable:
public operator void smash(Actor subject, Actor target){ ... }
But the parens would get quickly out of hand once we start nesting these. Is it possible to just have:
Ogre smash Knight;
Since the order of operation inference doesn't really care about the parens, it's a simple matter to make them not required in the syntax.
But then the programmer would want to do something like this:
Ogre smash Knight with rock;
The only way to really make this work is to have the smash function return something that takes the type that some global 'with' happens to return something that takes a Weapon and do all the work to curry things together so all the parameters can be used at once.
That sucks.
So the thought went towards two elements to make that better. The first is kind of simple. 'with' in the above example would need to be some global with a special type (or something similar) to get picked up properly (and/or prevent another identifier with the same signature being valid). But I'm writing the compiler. Why not have something 'just work', or even generate those types myself?
No reason at all. Tangent thus provides the concept of explicit identifier parameters. A function can be specified to take the literal identifier 'with' then, which takes priority over variables in overload scenarios. For example (using current syntax):
public foo('bar': bar) => void { ... }
public foo(int: bar) => void { ... }
// later in code
local int: bar = 42;
foo bar; // calls the 'bar' -> void version of foo!
But that still requires you to define the methods yourself and do all the currying. A whole lot of mechanical work, which of course promptly got pushed to computers. The big thing here is allowing definition of phrases:
public (Actor: subject) smash (Actor: target)
with (Weapon: weapon) => void { ... }
So that the method definition looks like it would be called. The compiler does all of the work making curried sub-functions, and making 'with' here a explicit identifier parameter.
In the end, we have something that looks like natural language. More importantly, we have something that behaves more like natural language. Tangent has the concept of 'makes sense'. If a statement doesn't shake out to void, it gets tossed at compile time. And the compiler will shake a statement until it does end up void, properly using the terms in their correct context, and handling the meaning overloads that are inherent to natural language. But nobody will use it if it is a pain. Phrase definition should provide a simple, intuitive mechanism for programmers to say what they want.
Having things closer to natural language should make the language very adaptable with regards towards building domain specific language in Tangent. Allowing a smaller gap between the domain and the code should make errors in translating smaller, as well as the spin up time for new programmers. As long as in the drive towards natural language the design doesn't forget that it is still a programming language.
Assuming that the natural language assumptions are correct, the question then becomes if the order of operation inference becomes too hard for programmers to read, debug, or even write what they really want to do in. Unfortunately that is something that I think can only be determined by writing code in the thing.
No comments:
Post a Comment