Jump to content

April 2017 »

2324252627 28 29
- - - - -

Epoch and Constructors

4: Adsense

One of the things I've been thinking about heavily for the Epoch language is the way constructors work. Epoch does not have null values or default initialization, so all constructors require explicit arguments to satisfy the type requirements of a given variable.

Here's a quick example:

structure foo :

entrypoint : () -> ()
integer(demo, 0)
foo(demostructure, 42, "test")


Constructors are actually special functions named after the type which they construct. There is some metaprocessing magic which handles the case where a constructor needs to create a local variable (which is always, at the moment). This is handled by the first parameter to the constructor, which is of type Identifier. The identifier becomes a local variable of the appropriate type within the scope where the constructor function is invoked, and the value is initialized as given by the constructor parameters.

This works very well for the moment, but has a couple of drawbacks. First, it is impossible to construct an anonymous temporary variable, as in the statement somefunction(foo(42, "test")) Secondly, for structures with a lot of fields, it can be tedious to have to explicitly initialize every field every time the structure is constructed.

I have plans for this, though, which should appear sometime in the indeterminate future as time permits.

Overloaded Constructors
Epoch already supports fairly powerful function overloading, and even allows for things like parameter pattern matching. This provides a very convenient mechanism for dealing with the constructor situation.

Anonymous-variable constructors can be implemented easily using a function overload which simply omits the first Identifier parameter. Since the only time that this is really useful is with user-defined structure types, and those constructors are magically generated by the compiler anyways, adding this overload should be trivial. This will allow for construction and usage of anonymous temporaries with ease.

The second case is a bit trickier. Ideally, I'd like to allow users to define constructors of any arbitrary number/type of parameters, so that it is possible to construct non-trivial objects easily, and effectively default-initialize fields as needed (since the language itself does not permit default-initialization).

This can be partially accomplished with the overloading mechanism, which will take care of allowing the programmer to invoke the constructor with the parameter set he desires; the tricky bit comes in with the magic that actually creates the local variables.

On Magic
There are three basic approaches I could take to this. First would be to have copy constructors set up automatically, and require that manually overloaded constructors invoke the copy constructor, so that manual constructors do not directly participate in the magic. This would look something like the following:

foo(variablename, foo("limited_parameter_set_demo"))

This is redundant, however, and requires expressing the type twice - something I've been trying to avoid. I'd like to minimize the amount of explicit typing required by Epoch, particularly via type inference, so demanding that all nontrivial constructors use the above syntax seems a bit wasteful to me.

The second major option is to automatically detect the presence of an overloaded constructor, and have it participate in the magic transparently. This would look a lot cleaner:

foo : (identifier varname, string parameter) -> (foo(ret, 42, parameter))

foo(variablename, "limited_parameter_set_demo")

Unfortunately, this requires a lot of magic. I can't think of any situations where this magic would get the programmer in trouble, i.e. where he wants a function that looks an awful lot like a constructor but isn't; however, I don't want to necessarily take that risk just yet.

Finally, the magic could be explicitly invoked. There's a couple ways this might look, but my favorite would be something like this:

foo : (identifier varname, string parameter) -> (meta_constructor(varname, foo(42, parameter))

foo(variablename, "limited_parameter_set_demo")

This defines a magical function return type called meta_constructor. When this magical type is used as a function return, it invokes the correct magic under the hood to construct the variable given by the first parameter in the calling scope. This all happens at compile time, so the runtime overhead would simply be offloaded to invoking the anonymous constructor and copying that value into the newly constructed variable. A tiny bit of optimization can make the copy go away entirely, so that the variable is constructed in-place, and the cost of using a nontrivial constructor becomes zero.

Final Thoughts
This is the dilemma I'm faced with regarding variable constructors. I hope to think up a good solution sometime soon, but in the meantime, if you've got any suggestions or votes on how this should work, I'd love to hear from you!

Part of the great fun of building a language is getting the opportunity to face challenges like this and figure out how to solve them. It isn't often that I have to solve a truly novel language design problem (in fairness, I probably have yet to actually stray into unexplored territory as far as language design goes) but when something like this does come up, it can be exhilarating to find a good approach to the issue.

I may or may not get a chance to work on Epoch a bit over the holidays (I'm sort of promising myself to have a working version of my GDC talk ready by the new year) but I will definitely keep you guys posted.

In the meantime, Merry Christmas, Happy New Year, and Good Coding [smile]

Note: GameDev.net moderates comments.