View on GitHub

Ü Programming Language

Mixins

Motivation

Almost from start of the development Ü has reach metaprogramming mechanisms - templates, static_if, enable_if, type inspection via typeinfo and powerful (but limited) compile-time code execution. But even with such possibilities Ü had some fundamental limitations, which limit some metaprogramming needs. Some of these limitations:

These limitations were not so painful, but sometimes forced to write code differently or create too much boilerplate. Because of that i searched for a solution, which allows to solve all these problems.

In search for a solution

Initially i thought to make reference/mutability modifiers syntax more complex - add some constexpr parameter for them in order to make mutability/reference modifier conditional. This might work, but it seems to be too complicated to implement and too boilerplate to use.

Also i have thoughts to implement (somehow) template functions with variable number of parameters. But this idea is also hard to implement and to use.

All these solutions are too partial and can’t solve all problems. Thus i decided to find a perfect solution - which allows to solve all of them.

The most stupid and powerful solution

I decided to investigate, how other programming languages solve such inflexibility. I have found, that the Zig language uses some sort of compile-time code generation for polymorphism, where languages like C++ and Rust use templates. The D language also provides a mechanism to generate code from compile-time generated string, this mechanism is named mixin. After some thinking i decided, that the D solution is almost perfect and is worth to implement it in Ü.

Mixins basics

Generally mixins work pretty simple. Mixin expression is evaluated (in compile time) and given text is parsed as normal Ü program or its fragment. Than parsed syntax elements are added in the place of the mixin usage.

Examples:

mixin( "fn Foo() : i32 { return 42; }" ); // Add a function via mixin.

struct S
{
    mixin( fields ); // Add struct fields using mixin.
    auto& fields= "i32 x; f32 y;";
}

fn Bar()
{
    Foo(); // Call a mixin-generated function.
    var S mut s= zero_init;
    mixin( "s.x= 1; s.y= 0.5f;" ); // Add mixin block elements.
    auto sum= f32(mixin("s.x")) + mixin("s.y"); // Use mixins in expression context.
}

Mixins implementation wasn’t so trivial. For mixins in namespace and class context it was needed to implement mixins evaluation and expansion as separate compilation step - between the names table preparation and later code building. For mixins in block and expression context it’s simpler - result syntax elements may be used directly to build block elements or an expression. Additional complication adds the necessity to parse mixin text properly, using macros available only in the file, where this mixin is used.

Results

After implementing mixins i managed to implement some standard library code, which wasn’t so easy to implement earlier.

Example #1 - homogeneous_tuple

Prior to mixins introduction it was the only way to implement homogeneous_tuple type alias - using type templates overloading.

template</type T/> type homogeneous_tuple</T, 0s/> = tup[];
template</type T/> type homogeneous_tuple</T, 1s/> = tup[ T ];
template</type T/> type homogeneous_tuple</T, 2s/> = tup[ T, T ];
template</type T/> type homogeneous_tuple</T, 3s/> = tup[ T, T, T ];
template</type T/> type homogeneous_tuple</T, 4s/> = tup[ T, T, T, T ];

Now mixins may be used instead, or at least for cases where writing a lot of overloads isn’t practical:

template</type T, size_type size/>
type homogeneous_tuple</T, size/> = mixin( homogeneous_tuple_gen</size/>() );

// Generate a tuple type name like "tup[T,T,T,T,T]"
template</size_type size/>
fn constexpr homogeneous_tuple_gen() : [ char8, 5s + 2s * size ]
{
	// implementation details
}

The second solution isn’t so boilerplate and isn’t limited by size, as manually-written type alias overloads.

Example #2 - zip iterator method.

This method creates zip_iterator, which iterates over two sequences simultaneously and returns pairs of values. Such pairs are represented via a template struct with two fields - first and second. This wasn’t possible to do prior to mixins introduction, since such fields may be values - if underlying iterator produces values, or mutable/immutable references. But with usage of mixins i managed to write such struct template - a mixin is used to generate both these fields.

Possible examples of this struct (after mixin expansion):

// Both results are values.
struct zip_iterator_pair
{
    FirstType first;
    SecondType second;
}
// Both results are immutable references.
struct zip_iterator_pair
{
    FirstType &@("a"c8) first;
    SecondType &@("b"c8) second;
}
// One mutable and one immutable reference.
struct zip_iterator_pair
{
    FirstType &mut @("a"c8) first;
    SecondType &@("b"c8) second;
}

Mixins possibilities

Since code in mixins is generated by another code, anything may be generated. Some examples, what is now possible with mixins:

Mixins disadvantages

Mixins aren’t so ideal. There are some problems with them.

constexpr calculations, which are used to generate mixins, are somewhat limited. For now they do not support unsafe code, including memory allocation and thus almost all standard library containers. This makes writing mixins generation code a little bit hard.

It’s hard to debug compile-time calculations, there is no such thing as debugger for them. The only debug tools are halt and static_assert.

Code produced via mixins is also hard to debug, since there is no location in any source file directly corresponding to mixin code, which may be shown during debugging. It’s even problematic to fix compilation errors within mixin code, since full mixin code can’t be shown in error messages.

So, considering all the problems mentioning above i suggest to limit usage of mixins. If regular template code may be used, it should be used instead of mixins. If code generation is too complicated or slows-down compilation, it may be better to use some external tool for this - some script or proper generator program (like fir GRPC code generation).