Skip to main content
GameDev.net gamedev.net
🔒 Locked

Hap: a simple concurrent programming language.

Started by EvincarOfAutumn Apr 14, 2013 at 9:53 PM 6 replies 2.7k views
Original Post
EvincarOfAutumn
EvincarOfAutumn

Well, I’ve gone and done it again—written a language when nobody asked me to. This one might be interesting to game developers, and I’m interested to hear feedback from anyone with an opinion about what ought to be done with it.

The language is called Hap and is designed as a readable dynamic language for game prototyping. It uses single-threaded cooperative multitasking to support simple event-based concurrency. The example from the README sums it up nicely:


var health = 100;

when (health <= 0) {
  print("Goodbye, cruel world!");
  exit;
}

print("Hello, sweet world!");

while (true)
  health = health - 1;

This code prints:


Hello, sweet world!
Goodbye, cruel world!

Hopefully that’s enough to get your attention. Another example shows how you can use the asynchronous control flow statements to write surprisingly readable code:


fun make_cycler(start, end) {

  var current = start;

  whenever (current > end)
    current = start;

  ret lam() {
    var previous = current;
    current = current + 1;
    ret previous;
  };

}

This function returns a function generating a cycle of values in the given range. For instance:


make_cycler(0, 2)

Will produce the sequence:


0, 1, 2, 0, 1, 2, …

Hap can be used on its own, but the idea is to support embedding in larger systems, much like Lua or JavaScript. Being only a few weeks old, the language naturally has some issues that would need to be addressed before it could be used in production. In particular:

  • There is no standard library, and thus no way to perform a number of common math and container operations.
  • Embedding the language is not as simple as it should be.
  • Asynchronous control flow is not first-class, i.e., there is no way to refer to or halt a “whenever” statement.
  • The prototype interpreter is not designed with performance in mind.

I will gladly address these and any other issues if there is interest in the general concept. Tell me what you think!

EvincarOfAutumn
EvincarOfAutumn

I'll drop kick my first thought: "Why?" and try to be more useful... smile.png


Thank you! smile.png

The first thing that jumped out at me: "fun"... Err, please oh please use something a bit more descriptive: at least "func" or better "function", it's not horrible to type out full words in the language once in a while. Proc and other descriptive names are valid choices also, but "fun" to me is the name of a variable that someone needs to be kicked for, unless it really does describe something 'fun', like the 'fun' meter or something.


That’s a valid point. I don’t want to bikeshed over syntax, as it’s easy to change, but here’s my rationale for using short keywords:
  • You use them frequently, so they ought to be succinct.
  • They’re unlikely to conflict with good, useful identifiers. None of this “klass” or “function_” business.
  • They’re just abbreviations, not abstract mnemonics or operators.

Beyond that, I'd tend to wonder why you are writing a custom interpreter? I've put together a couple similar items to this (specialized network DSL's, not intended as generic languages) and it was extremely simple to pop it into the LLVM JIT and not have to write anything except the front end parser. It's just a thought since it tends to make things quite a bit easier on you.


Also fair. I’ve written a number of compilers and interpreters and this is just what felt right for a prototype. Whenever a statement is evaluated outside an atomic section, the interpreter runs an interrupt during which the active asynchronous conditions are evaluated. It was very easy to write a simple VM with this kind of custom behaviour built in—easier than implementing it atop an existing model. In the early stages of a language, there is more value in flexibility. Only as the language matures can you know what the “right” way is.
ApochPiQ
ApochPiQ
I like the when/whenever concept. I have a similar brewing idea for Epoch but in a more generalized CSP/message passing style.

I'm unfortunately way too busy working on my own language to mess with someone else's more than briefly, but I did want to say that the idea of trigger-based instead of polling-based game logic makes me more than a little excited.


As for LLVM... the learning curve can be a bit steep if you want to do anything nontrivial, and as I've been whining about in my journal/Twitter feed for the last week, things like garbage collection are outright painful... but it's still a great kit and well worth targeting if you want free performance boosts :-)
EvincarOfAutumn
EvincarOfAutumn

I know it is trivial but 'fun' just strikes me as wrong. At first I thought it was just a typo, then I checked the linked example and hey, it wasn't a typo.

Hah, looking like a mistake isn’t good. Then again, “fn” has precedent in Clojure and Rust, so perhaps “fun” isn’t too far off the mark. The “function” keyword in JavaScript must have looked perfectly fine when the language was designed, but it’s a pain now that the use of the language has evolved. I’ll think about it and take input from others. Syntax is unimportant and then it’s important, you know?

As to the interpreter stuff. Yep, experimenting is a great thing and not being tied to something is even better. Having said that, please understand I'm making a suggestion I believe would save you time even in the experimental phase and I'm not arguing the reasons or goals.

I appreciate it! I have toyed with LLVM before and will most certainly move to it in the future. (ApochPiQ took a similar route with his Epoch language, for example.) What’s done is done, and the existing interpreter certainly isn’t bad—just not ideal.

Now, the things that concern me most are the design of a standard library and support for embedding. I would be interested to hear your opinion on those. As far as a standard library goes, what I’m working on at the moment is quite simply exporting native functions through a global object. For example:


var keys = hap.map.keys;
var map = { this: "this", that: "that" };

# ["this", "that"]
trace keys(map);

The “hap” object would give you a few broad namespaces such as “map”, “list”, and “io”. When embedding Hap, you would use the same mechanism to export native functions to your Hap code. You could also make foreign function declarations by leaving off the body of a function definition. In both cases, marshalling would be done by the Hap library.

I would also like to support immutable names by way of a “val” keyword (à la Scala). In the future I will add support for type annotations, and move to static typing with global type inference—I have an appropriate inference algorithm. This may seem radical, but it offers distinct advantages. For one thing, I don’t think it’s possible to manage the complexity of Hap-style concurrency and dynamic typing together. These small concurrency examples are simple to understand, but I fear they won’t scale without some help.

EvincarOfAutumn
EvincarOfAutumn

I like the when/whenever concept. I have a similar brewing idea for Epoch but in a more generalized CSP/message passing style.

I'm unfortunately way too busy working on my own language to mess with someone else's more than briefly, but I did want to say that the idea of trigger-based instead of polling-based game logic makes me more than a little excited.

The current implementation runs a periodic interrupt and polls events, but it’s a relatively minor change to do it push-based, and I mean to. Hap is deliberately not general at the moment, for the sake of getting something out the door—it’s an iterative process. smile.png

The original idea included “repeat when” and “repeat whenever” for running loops in response to events, but these proved challenging to implement correctly with the current model, so I held off. “repeat when” would begin evaluating when a condition became true, and repeatedly evaluate until that condition became false. “repeat whenever” would do the same thing, but also resume execution when the condition became true again.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.