Over five years ago I started talking about Heron on Artima.com. After several re-designs, and many side adventures, I am finally making the first Alpha release of version 1.0 of the Heron interpreter.
This is an open-source interpreter for Heron written in C#, that runs only on Windows. Unfortunately I do not currently support Mono, because of some dependencies on the new .NET 4.0 parallelization libraries.
A Short History of Heron
Since I have been talking about Heron for so long, and this is a landmark release, I thought I might talk a bit about the history of Heron.
Heron has been under development for over five years. In fact it started as a seed of an idea during a programming languages survey course I took at McGill University nearly 15 years ago.
The original idea I had was that programming languages were in general more complicated than they needed to be. It seemed to me that there was no real reason we couldn't write pseudo-code like I found in textbooks like the famous Introduction to Algorithms. Of course I hadn't yet heard about Python or Ruby.
As I started learning new languages I kept finding reasons to give up on my programming language project, and then new reasons to start it up again. It seemed that no language quite fulfilled my quixotic desires.
About six years ago, I started work on Heron. The first version of the language was almost a dialect of C++ with better support for meta-programming. It was implemented as a Heron to C++ translator. I had a lot of trouble getting traction on the idea, and got side-tracked on another project: the Cat programming language.
What Kind of Language is Heron?
Heron is a general-purpose object-oriented language that strives to be appropriate for large scale project development (at least once it is mature). Heron borrows a number of ideas from functional languages, and offers some new twists on object-oriented programming.
While Heron is being designed for larger-scale team-based software development, it should also be appropriate for simple programming tasks. I want to be able to use it do develop simple programs, which can be reused easily in larger program without modification.
Characteristics of Heron
The following are the most notable features of Heron
Instantiable Modules - Heron modules serve as namespaces for types, but can also can contain functions and variables. A module is like an outer class, while ordinary classes in Heron are inner classes.
Built-in concurrent list operations - There are four primitive list operations in Heron: map, reduce, select, and accumulate. The first three operations can be evaluated concurrently by the Heron interpreter.
Variant types - Heron supports a special universal variant type called Any. The universal variant can hold any value, and maintains type information about the contained value. It supports querying of the type of the contained value via the is operator, and supports extraction of the contained value via the as operator.
Explicit signature-based polymorphism - The as operator can be used to cast a class or interface instance to an interface instance, if the source implements the function signatures of the target interface.
Optional type signatures - Type signatures are optional in Heron. In this case the type is assumed to be Any.
Compile-time programming support - Heron can execute arbitrary code from the program at compile-time. A function named meta() is executed automatically if found during compilation in the primary module. The argument to the meta() function is an object that represents the code model (effectively the abstract syntax tree) of the Heron program.
Non-nullable types by default - Reference variables (e.g. class or interface types) can't be assigned null unless explicitly marked with the ? modifier.
Heron is also notable in its explicit omittance of certain common language features, which are mostly redundant because of the features listed above. Some notable omissions and a brief rationalization are:
No predefined visibility specifiers - Visibility specifiers are achievable via compile-time programming libraries
No run-time reflection - Reflection comes at a performance cost, because it inhibits certain optimizations and static analyses. Compile-time reflection also suffices in a wide number of cases where run-time reflection is desired.
No virtual functions - Virtual functions introduce the possibility of subtle bugs due to contract violations (such as precondition strengthening or postcondition weakening). Without virtual functions it is possible for objects to also omit the overhead of a vtable. Interfaces provide run-time polymorphic behavior.
No downcasts (i.e. casting from a Base type to a Derived type) - Downcasts are a potential source of bugs, and design errors (e.g. violation of the Liskov substitution principle).
No universal base type - In other words there is no Object base class. Since there are no downcasts, a universal base type would be not very useful. Many of the cases where such functionality is desired, the Any type suffices.
No global data - Heron modules provide the syntactic convenience of global variables, but without introducing hidden coupling. All instances of classes are associated with a module variable.
No static data - Static data is effectively global data in disguise. Most best practices suggest avoiding it as much as possible.
About this release of Heron
It's a little audacious to declare a language release as version 1.0, because there is so much work to do on it always. Nonetheless
I've declared the current release of Heron as the first Alpha for Version 1.0 for two reasons:
I'm convinced that the language semantics and syntax won't change significantly until the next major version
The language has undergone a fair amount of testing on my side and is ready for early adopters to start trying it out, reporting bugs and making minor feature requests.
For more Information
If you want more information about Heron I strongly urge you to download it and take a look at the various test programs that it comes with. You can also wonder around the web site at Google code hosting, or ask me questions here! I look forward to hearing your feedback.
> Without having looked at it thoroughly, I like the > decisions you have taken for this programming language.
> Do you plan to do Heron as a standalone native language > that is independent of any virtual machine?
I defintely do. I'm thinking of writing a Heron to C++ translator. However I've also been thinking of writing a cross-platform VM based on Cat. It kind of depends on where the waves take me from here. I'm half expecting to find a corporate sponsor for the project in the near future, so this will probably affect plans.
> However, this new Heron seems to be a very different > beast. It seems to be an enterprise-friendly application > language like Java and C#, rather than a > system/application one like C and C++.
I think that Heron can still be an effective application language. I have done a lot with the design to ensure that it can be implemented very efficiently. If you are aware of the Go language from Google, it is a good example of a more modern approach to system languages, and that has some similarities to Heron.
I would however be very interested in hearing, what you specifically would look for in a modern systems language.
> How limiting are the differences with Mono? Do you feel like this is a temporary or persistent issue between Mono and .NET?
I used .NET 4.0 for the parallel extensions framework. I could either remove the parallelism from the interpreter or roll it by hand if I wanted to support Mono. I might also have to rewrite a few sections of code. However, my experience in supporting Mono when developing Cat was met with limited enthusiasm. The extra effort of providing Mono compatibility isn't worth it to me at this point. I'd rather have the easy parallelism.
As to the bigger question of whether Mono can ever catch up to .NET, it appears that it will always be significantly behind, and that gap seems to be growing as the complexity of the .NET framework grows.
> > However, this new Heron seems to be a very different > > beast. It seems to be an enterprise-friendly > application > > language like Java and C#, rather than a > > system/application one like C and C++. > > I think that Heron can still be an effective application > language. I have done a lot with the design to ensure that > it can be implemented very efficiently. If you are aware > of the Go language from Google, it is a good example of a > more modern approach to system languages, and that has > some similarities to Heron. > > I would however be very interested in hearing, what you > specifically would look for in a modern systems language.
Here is what I am looking for in a modern systems language:
-no virtual machine; code compiled to native platform binaries. In the company I work, our client does not like to having to manage virtual machine dependencies and updates.
-optional garbage collection. In most cases, garbage collection is an advantage, except for those cases that code must be accurately timed.
-compile time code execution. Sometimes a problem dictates that the source code should act as a script for preparing the execution environment, as well as creating the appropriate data structures for the application. One example of this is database applications. With compile time code execution, the database can be created from the code at compile time, together with the data structures that can be used to manage it, saving valuable time and providing compile-time checking. Another example is the creation of parsers for communication protocols; a protocol can be expressed as a series of structs, and the compile-time code can create the appropriate parsers for each struct.
-type deduction. It's boring to write the types and it takes valuable time from the thought process.
-templates (ala C++). It's a must for performance in constrained devices and system software.
-full support for functional programming; treating functions as first class entities can save valuable time; for example, no need to write for loops over and over.
-direct interfacing with C and with the native platform. No strange FFI which takes a lot of time to get it right.
-no header files. One of the most time consuming tasks in C++ is to maintain headers consistent with implementation files. In plain C, this is not that bad. In C++, where a good part of the code leaves in headers, it's quite difficult and makes compiling slow.
-full support for dangerous actions like pointer arithmetic, unchecked array access, and unsafe casts, but these must *not* be available as defaults. The programmer should have to explicitly request those dangerous actions. It should be possible to prohibit these actions via compiler flags.
-typed memory management. This is something I haven't seen anywhere. In C++, just like in C, memory is essentially treated as of type void*.
-unified type system, i.e. no separation between primitives, classes and structs. A class should be independent of how the relevant memory chunk is allocated, either dynamically or statically. The separation of structs being value types and classes being reference types is not necessary and introduces an artificial separation that forces the programmers to deal with it. It also may hurt performance: a class provided by a 3rd party library can be allocated statically in a program, but since it is a class, static allocation of it cannot be done.
-a context-free grammar, because I haven't seen any C++ tools providing accurate Intellisense.
-pureness provided at class and function level; 'const' does not cover the whole range of pureness requirements.
I like Heron's explicitly signature-based polymorphism. It solves the big problem of having to design your classes upfront in such a way that they might be useful in the future; i.e. it helps avoiding over-engineering.
> If you are aware > of the Go language from Google, it is a good example of a > more modern approach to system languages, and that has > some similarities to Heron. > > I would however be very interested in hearing, what you > specifically would look for in a modern systems language.
I am aware of Go, and think that it can serve the purpose (server applications) very well. Having said that, it is not exactly what I would call a system language.
My list of features for a modern system language (surprisingly?) looks much like one that Achilleas wrote. However, to be more specific, let me clasify them somewhat:
1) Features that are "a must" for a system language. Languages like C and C++ pretty much already have them: - Easy access to system resources, including natural access to C libraries and APIs. - Tight control of resources, including option of manual memory management. Any non-deterministic GC is out of question except as an option that is disabled by default. - Efficiency both in CPU and memory consumption. - No dependency on any runtime, be it a VM or a library.
2) Features that would make a "good" system language (better than C and C++): - Flexible system of abstractions that strictly follow "pay only for what you use" policy: Ada83 style types, interfaces, mixins, generic functions,... That excludes features such as run-time reflection (even C++ RTTI is too much) and possibly exceptions. - A deterministic system for automatic resources cleanup. Something like RAII would do the job, but it would probably be possible to come up with a better built in system of marking the life scope for an object and the cleanup operation that gets called when it goes out of that scope. - A "nice" syntax (not C-based), case insensitive, not too verbose, with simple rules for operator precedance; no superfluous semi-colons and parenthesis. Easy to parse, to allow easy creation of good tools. - A very static ang strong type system that would catch as many errors as compile time as possible. - A rich set of static libraries that can be optionally linked if needed. A choice of static and dynamic linking should be provided.
> That excludes > features such as run-time reflection (even C++ RTTI is too > much).
Why do you say that? how does run-time reflection or C++ RTTI affect performance?
> - A deterministic system for automatic resources cleanup. > Something like RAII would do the job, but it would > probably be possible to come up with a better built in > system of marking the life scope for an object and the > cleanup operation that gets called when it goes out of > that scope.
Aren't C++ destructors exactly what you are asking for? if not, what is your problems with C++ destructors and what improvements you would like to see?
> > That excludes > > features such as run-time reflection (even C++ RTTI is > too > > much). > > Why do you say that? how does run-time reflection or C++ > RTTI affect performance?
A capability to have runtime reflection (even if it is never actually used) does not come for free. It basically means embedding symbol information to executable files. Compare the size of a simple C++ program when turning the RTTI on/off.
Also, it would disable many optimizations that would otherwise be possible: for instance a C++ compiler optimizes away unused types or parts of template code that is never instantiated.
> > > - A deterministic system for automatic resources > cleanup. > > Something like RAII would do the job, but it would > > probably be possible to come up with a better built in > > system of marking the life scope for an object and the > > cleanup operation that gets called when it goes out of > > that scope. > > Aren't C++ destructors exactly what you are asking for? if > not, what is your problems with C++ destructors and what > improvements you would like to see?
As I said, C++ destructors do the job, but as with most C++ features it is not exactly obvious how to use them correctly. Ideally, there would be some way to explicitelly mark the life scope of an object so it gets destructed the moment it leaves it (or when the "owner" reference leaves it). The semantics would be the same as with today's C++ destructors, but syntax would be different.
> > > That excludes > > > features such as run-time reflection (even C++ RTTI > is > > too > > > much). > > > > Why do you say that? how does run-time reflection or > C++ > > RTTI affect performance? > > A capability to have runtime reflection (even if it is > never actually used) does not come for free. It basically > means embedding symbol information to executable files. > Compare the size of a simple C++ program when turning the > RTTI on/off.
Would it be ok for you if the information required for reflection same on a separate lib file? then you wouldn't have reflection until you link with that specific file.
> > Also, it would disable many optimizations that would > otherwise be possible: for instance a C++ compiler > optimizes away unused types or parts of template code that > is never instantiated.
I cannot see how this affect reflection. Unused types and unused template code does not exist in compiled binary code, so how does this affect reflection? I am not saying it does not affect it, so can you please elaborate on how does it affect reflection and how does it affect optimizations in the context of reflection?
The article does not say that by including reflection in your program and not using it, it suddenly becomes slower. The article says that by *using* reflection, it is slower.
In other words, reflection can be there, and if you don't use it, it will not affect performance.
I will have again to ask you, why do you think reflection is bad for performance and how?
> > > > > > - A deterministic system for automatic resources > > cleanup. > > > Something like RAII would do the job, but it would > > > probably be possible to come up with a better built > in > > > system of marking the life scope for an object and > the > > > cleanup operation that gets called when it goes out > of > > > that scope. > > > > Aren't C++ destructors exactly what you are asking for? > if > > not, what is your problems with C++ destructors and > what > > improvements you would like to see? > > As I said, C++ destructors do the job, but as with most > C++ features it is not exactly obvious how to use them > correctly. Ideally, there would be some way to > explicitelly mark the life scope of an object so it gets > destructed the moment it leaves it (or when the "owner" > reference leaves it). The semantics would be the same as > with today's C++ destructors, but syntax would be > different.
How are C++ destructors not exactly obvious? can you please give me an example on how it is not exactly obvious? For me, the declaration of a locally allocated object is as explicit as it gets that the destructor will be invoked.
(my apologies for the number of questions; there may be something that escapes me so I am asking for clarifications.)