Locic 1.2 Very soon

The next release of the Loci Compiler Tools will soon be available!

Full release notes will be part of the provided locic-1.2 archives (and up on http://loci-lang.org) but (to briefly mention it) the main feature of this release is predicates, which is essentially a much clearer and easier to use form of C++’s proposed concepts feature.

Predicates are just boolean expressions (i.e. that evaluate to either true or false) depending on template variables. For example:

interface CanJumpLeft {
    void jumpLeft();
}
 
interface CanJumpRight {
    void jumpRight();
}
 
template <typename T>
require(T : CanJumpLeft and T : CanJumpRight)
class Example(T object) {
    static create = default;
 
    void jump() {
        @object.jumpLeft();
        @object.jumpRight();
    }
 
    // The require predicate can also be specified
    // for just this method, meaning the class
    // can be created for non-jumpable things but
    // this method is disabled. I.e.:
    //
    // void jump() require(T : CanJumpLeft and T : CanJumpRight) {
    //     ...
    // }
}

Predicates come in three forms:

  • Const predicates – Specify that a variable or value is const depending on the outcome of a predicate. The common case is methods that return a non-const result if their parent is non-const, but return a const result if their parent is const.
  • Noexcept predicates – Specify that a function or method throws depending on the outcome of a predicate. For example, a method may only be noexcept if another method it calls is noexcept.
  • Require predicates – Specify that a type/function/method is only valid depending on the outcome of a predicate. For example, an array is only copyable if its elements are copyable.

This kind of functionality and capability is far ahead of C++; combined with the design of Loci templates (being usable across compilation boundaries!) it provides an extremely clear and concise way to write structured code. It’s an important core aspect of the Loci type system and substantial portions of the standard library rely on this (and that will be an increasing trend).

Named Predicates

For the next release I’m planning to augment this functionality to allow specifying named predicates. Right now you can create type aliases like:

template <typename T>
using MapFromInt = std::map<int, T>;

My idea is to extend this to predicates:

template <typename T>
using is_comparable = T : comparable<T>;
 
template <typename T>
using is_noexcept_comparable = T : noexcept_comparable<T>;
 
// To be used like:
template <typename T>
bool is_ordered(const T& a, const T& b)
    require(is_comparable<T>)
    noexcept(is_noexcept_comparable<T>);

The vast majority of the compiler machinery is already in place for this sort of feature and there is a clear gain in readability, so I expect this to be one of the first features I work on in the next release cycle (1.3).

Remaining Work

So the compiler is now mostly ready for release (release date is expected to be this weekend). The remaining work is:

  • Improvements/fixes to the standard library.
  • Updating documentation.
  • Adding more tests.
  • Clearing up some of the messier parts of the code.

Dropped Features

Unfortunately exception specifications are going to be dropped from this release and pushed forward. Fortunately on a relative basis they aren’t a particularly important feature and they also have a slight conflict with noexcept predicates that should be resolved. I think people (including myself) care a lot more about other features (e.g. lambdas, import-from-export generator tool, better standard library) so I’m going to push the implementation of exception specifications into the more distant future.

Next release (1.3)

I’m expecting the next release to be focused on even further development of templates in the form of value templates (already mostly ready in the compiler), variadic templates and template argument deduction.

By adding these, functions, methods, interface methods etc. will become primitive types (i.e. like ints, floats, lvals etc.) and hence this will remove a nasty (and probably slightly broken) special case from the compiler. Hence developers will able to treat functions in the same way as other types (e.g. putting them in containers). The other (slightly simpler) new primitive type will be a statically sized array type (like C++’s std::array).

I’ll also start building a ported version of OpenP2P and in the process start building (alpha quality) tools that make it easier to create C or C++ to Loci bindings (i.e. calling from Loci to C and C++). The idea for this is to use Clang to analyse C and C++ source code and produce both some C++ code (for C this isn’t necessary) and a Loci ‘header’ file with the relevant imports. In the C++ case we need to generate some C++ code that generates some ‘export C’ (i.e. unmangled, standard C calling convention) functions that are callable from Loci and call the relevant C++ methods.

The C case is fairly simple but for C++ there are some issues, of course:

  • C++ templates require compile-time expansion. They also don’t express any constraints that could be translated to require() predicates.
  • C++ classes must have a compile-time known size (Loci classes have fixed size, but in the general case this size doesn’t need to be known until run-time) – This is actually not a problem for calling from Loci into C++, but would raise issues for the reverse situation (solution is probably just using PIMPL idiom, meaning a heap allocation, which is what C++ programmers would do anyway).
  • C++ supports function/method overloading.

These problems can all be solved, probably via some sort of human direction (i.e. specify that particular template instantiations be created, indicate names for overloaded methods). Overall with the help of Clang it should ultimately be possible to do the vast majority of the work automatically and leave the developer to clear up the odd cases.

In terms of release dates, I’m aiming for mid-Summer (i.e. around July).