Epoch and Constructors, Revisited
There are times in the creation of any significant piece of software where one encounters difficult problems. These may range from practical, to architectural, to algorithmic; some days it seems that there is no end to the challenges that can crop up when writing nontrivial programs.
Recently, I wrote about one such issue that has cropped up in the development of the Epoch programming language, namely the issue of constructors. As a quick rehash, the basic problem is that the language needs a way to uniformly allow overloaded functions to construct local variables within a certain scope. This permits the programmer to create constructors that do not fit the default profile of Epoch initializers (i.e. all structure fields must be explicitly initialized, etc.). Function overloading solves half of this problem, by allowing the type name to appear as a function with multiple overloads that provide the semantics desired by the programmer.
The issue is that this doesn't actually create a local variable as expected, at least not by default. I had a few different methods in my mind for solving this, none of which really appealed to me, and left the question open.
Thankfully, oftentimes when faced with a nasty problem, one only has to commit adequate time and thought to reaching a truly elegant solution. The great benefit of working on an open-source project in my free time is that I have ample opportunity to sit and ponder these problems without having to worry about deadlines, costs, or the bottom line for the shareholders. It's sort of distilled the essence and purity of programming into a compact experience for me; I get to do cool work on hard and stimulating problems without all the nasty real-life obstacles that clutter up, say, an actual job.
The solution to the Epoch constructor problem seems painfully obvious to me in retrospect, but that is often the case with really elegant solutions.
First, let me review a critical concept that I've wanted in the language from day one: the notion of function tagging. This mechanism will, in theory at least, allow the programmer to attach certain semantics to a function with an explicit bit of syntactic sugar. For instance, I might tag a function as "pure" to prevent it from utilizing side effects or mutable state; this can be dead useful when it comes to reasoning about the correctness of concurrent programs, for example.
Currently, only one tag is implemented, and that is external. The external tag indicates that a function is not necessarily implemented in Epoch, but should instead redirect to an external, C-API-compatible function implemented in a loadable module (DLLs on Windows). This tag sets up some magic behind the scenes that directs the compiler to marshal the function parameters and return values into the C API, and automatically calls the DLL function under the hood when the Epoch function is invoked.
(As a curious side effect of the way this works, it is actually possible to invoke a DLL function and execute Epoch code after the DLL function returns but before the calling Epoch code resumes. This is more of a technical curiosity to me than a really useful feature, but I imagine it may come in handy for things like exception handling and other stuff down the road.)
So all this time the answer was staring me in the face: constructors don't need pesky copy-into-variable semantics, or compiler magic, or even a magical return value type that performs the construction under the hood.
They just need a tag.
The upshot of this is that overloaded constructors become laughably easy to implement, and perform exactly like the built-in constructors do, just by making use of the existing tagging functionality of the language. All this should be trivial enough to implement - so much so, that I might just have a shot at doing it over the Christmas/New Years break.
I'll leave you with a simple example of how this will end up looking in a small Epoch program:
structure Foo :
Foo : (identifier(variablename)) -> (Foo(0, "default", false)) [constructor(variablename)]
entrypoint : () -> ()
// Construct the "normal" explicit way
Foo(normal, 42, "test", true)
// ... and now construct using the overloaded constructor defined above
debugwritestring(normal.TextField) // outputs "test"
debugwritestring(usesdefaults.TextField) // outputs "default"