Spaces for all the things, and everything in its space

Published September 29, 2014
Advertisement
So Epoch has managed to self-host (as of an embarrassingly long time ago) and the Era IDE is slowly turning into something actually worth using. Over the past few days I got a rudimentary symbol lookup feature done, where you can press F11 to process a project and F12 with the text caret over a symbol to jump immediately to the definition of the symbol. This makes navigating the compiler source a lot easier, but still far from ideal.

As often happens with this sort of project, needs in Era fuel features in the compiler, and vice versa. At the moment my biggest annoyance with Epoch is the lack of good encapsulation support - there is no module system, no namespace system, no object system, nothing.

I've written here before about the ideas I have for solving this, but it's worth reiterating since the notion has evolved a bit in my head since the last time I was really prolifically talking about it.

Essentially, the unit of organization in an Epoch program right now is a function. The difficulty is that there is no mechanism for grouping functions into higher-level constructs.


My idea for solving this is the task. A task is an abstract collection of functions, data structures, and/or variables. It acts like a namespace, but also acts like a class in that you can instantiate a task and send messages to the instance. Functions in a task can be static, meaning that (as with most languages using the term) you can invoke them as messages without an instance of the task.


For example, here's the unit test for basic tasks in the compiler suite right now:

//// BASICTASK.EPOCH//// Compiler test for a very, very simple task//task SuccessTask{ succeed : [static] { passtest() }}entrypoint :{ SuccessTask => succeed()}
The entrypoint function, as always, is where the program begins. All this program does is send the "succeed" message to the SuccessTask task. Since "succeed" is static, it can be handled without a task instance, which simplifies the code a bit. Once the succeed message is received, it calls the unit test harness function and notes that the test has passed, and that's the end of that.


So why is this a big deal? Because tasks don't have to be confined to a single execution unit. A task, as an abstract notion, might actually represent a coroutine, or a thread, or another process, or even an RPC interface to a machine across the world. All of them get uniform syntax and semantics from the language, and all execution infrastructure is transparent. A task can be turned into a thread or whatnot just by applying the suitable tag to its definition. The rest Just Works.

The idea is nothing new; it's heavily based on CSP, the actor model, Alan Kay's "objects", Erlang's concurrency model, and so on. What's important is that all grouping of code and data in Epoch programs follows a single pattern.


This works for everything from small globs that would typically be classes in most languages, up to modules, and through all the way to entire systems interacting; because it's all uniform message passing, none of the guts matter to the programmer unless he wants to dig into them and configure the way tasks are executed.

Tasks of course can have no shared state, and one of the things I look forward to doing with this feature is destroying the global{} block for once and for all. This makes it trivially easy to spin up tasks on multiple threads (or machines, or GPUs, or whatever) and know for certain that they can't corrupt each other's state - the boundaries are always clearly defined by the message APIs exposed by each task.


A lot of the details remain to be worked out, but this is the general direction I'm heading, and so far I really like it. I'm kind of firing first and aiming later, so expect some course correction and refinement as I home in on the final setup. Most of this is being informed by actually writing the compiler (and Era) in the task model, so the decisions I make will be based on real programs and real concerns instead of abstract theoretical ones.


Anyways. To get all this to work, I first need to build the concept of a self-contained namespace into the compiler. So that will be my concrete project for probably the next few weeks, if the experience retrofitting the C++ implementation to use namespacing is any indicator. I hope Epoch proves to be more productive than C++ for this sort of change, but we shall see.

Once namespaces exist, I just need to build the idea of a task which owns a namespace, and the idea of sending messages into tasks. After that I'll build the notion of instantiating tasks, and then see what strikes my fancy from there.


Oh... and all of this is subject to take a long-ass time, because I'm currently prone to being hounded for belly-rubs by this giant fuzzball:

Thor 1.jpg
Previous Entry Playing with colors!
3 likes 4 comments

Comments

Washu

I do not like the [static] annotations.

I would much rather a system more like python/ruby where "instance" methods take a reference to the task as the first parameter. Thus there is no such thing as a static or instance method, as they both can be called with the same syntax... the end result being that we do not need a special "self" or "this" keyword. That the only difference between instance and static methods is the first parameter, and that you can call an instance method as though it were static (see below).


task SuccessTask
{
	succeed :
	{
		passtest()
	}

	worstLanguageEver : SuccessTask self -> string result = "C++"
	{
		result = "Java"
	}
}


entrypoint :
{
	SuccessTask => succeed()

	SuccessTask task;

	// Note that this is the same as calling it like...
	task => worstLanguageEver()
	// This.
	SuccessTask => worstLanguageEver(task)
}
September 29, 2014 07:35 PM
ApochPiQ
Yeah the more I read [static] the less I like it.

I'm not a huge fan of the explicit self parameter, but I need to think about it more. I don't like having to re-specify the type of my task every time I add a message to the task.


Blah. Not enough brain cycles to get a real answer yet. Maybe I'll do some scratch code tonight and see what feels good.
September 29, 2014 08:43 PM
Washu

Do note that the parameter need not be named self. In fact, the programmer can call it whatever they want (I originally called it "task").

Also, member variables and static variables need to be figured out.

September 29, 2014 09:59 PM
ApochPiQ
Yeah it's not about the parameter name so much as the necessity of specifying the type. It just feels cleaner to not have redundant information in the message handler signature. Templated tasks might become a thing, for instance, which would make this especially troublesome.

I'll take some time to rough out some options and post them up in a bit.
September 30, 2014 01:25 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement