Lifetime crisis

This is one of such embarrassing moments when I have to admit that because of lack of proper design I fell into trouble. I’ve been recently working on adding lifetime checks to Skila and I found more problems than expected.

let iter = coll.getIterator();

This benign piece killed the idea of not adding any new features — I already have pointers and references, but since collection can be on stack or on heap I would need to add something in between. A pointer with limited lifetime — this way I could tie up coll with iter — if it is regular pointer with global lifetime, go ahead be global. But if not — iterator needs to be local.

Or I could perform boxing on fly… C# has this feature and I hate it — it is difficult to see with naked eye performance offending code.

let elem = coll[index];
coll.clear();

I use references with indexer so this would lead to cherished undefined behavior. The easiest way would be to change semantics and working with values, two other approaches would require reworking entire language — I could go Rust way with ownership or functional way with immutable data (collection could not be altered, thus all references would be guaranteed to be valid).

foreach (let elem in coll)
  coll.clear();

Here Rust simply shines detecting the problem in compile time. This is the least problematic piece though, because one way or another it will always work correctly, maybe only surprisingly.

Honestly those problems undermine entire Skila existence — I wanted to get easy, likeable yet performant language. And I don’t see it on the horizon.

Tagged , ,

Object (post) initialization

Skila-1 had auto constructor as its object initialization version, but few days ago I was writing some ordinary Config type in C# and when I added builder pattern I realized how little C# is missing to get proper object initialization.

Thus I scratched the idea of reimplementing Skila-1 approach and I added object (post) initialization — it is much simpler to implement and it feels completely like C# (whenever I can I prefer to stick to already popular language). Using C# syntax it is just:

public class Point
{
    public init double X { get; }
    public init double Y { get; }
}

var p = new Point() { X = 5, Y = 7 };
p.X = 3; // error

Yes, all it takes is little modifier added to field/property indicating that despite it is readonly it is allowed to be assigned (initialized) during post initialization. The type itself is immutable, there is no additional mechanism added, everybody is happy.

The older approach has its advantages (single initialization for starter) but since it can be added in any time I will stick with this lightweight approach for now.

Tagged , ,

Complete null safety

Embarrassingly, it took me almost seven years to notice the full meaning of Option/Nullable type combined with mechanism like pattern matching.

The first layer is pretty obvious — consider such language as C# and its struct type. It is totally safe to use because it does not allow null. The separate world is nullable struct, here you can use null but your code is safer anyway because there is distinction between pure struct instance and nullable one. Until you write:

nullable_printer.Value.Print();

You pass null and boom, you have exception in your hands. The problem in this case is you can access the internals of nullable type.

Skila does not have pattern matching, but similarly it treats Option as black box — you cannot dereference it, you cannot access any of its member, the one thing you can do is decompose it. Conditionally:

if let printer ?= nullable_printer then
  printer.Print();
end

The optional declaration is successful only when we don’t have null. This way working with null is completely safe because compiler guarantees the logic flow is solid and you don‘t see any NullException. One thing less to worry, so I call it a progress.

Tagged , , , ,

Strings — performance hurts

I barely have time to write what happens with Skila but this one is so off I cannot resist — strings. Wide Unicode or UTF-8? The latter right, because they take less space so they require less fetches from the memory. How do we index them? By character or by byte? The former will not give us constant time thus by byte.

I know there are currently languages going this path — like Rust or Julia — but I don’t feel comfortable with it. For one, there is no abstraction layer here, the implementation details leak right into API. Secondly it forbids having common IString interface with UnicodeString because it would be indexed differently. Oh boy…

This choice even pushed me to a strange at first glance decision for having reverse methods lastIndexOf starting from exclusive index rather than inclusive. Oddly this is somewhat in sync with ranges (inclusive to exclusive).

UTF-8 string, where will you lead me…?

Tagged , ,

Extending generic types with traits

I am pretty happy I managed to add the feature I was not able to while working on previous Skila — traits. Initially I thought about them as conditional methods:

class Greeter<T>
{
  void hello(T &t) where T : ISay
  {
    t.say();
  }
}

But when reading “Programming Rust” I noticed that syntax for Rust type implementations and this gave me the idea of decoupling those “conditional methods” from the main type:

class Greeter<T>
{
}

trait Greeter<T>
  where T : ISay
{
  void hello(T &t) 
  {
    t.say();
  }
}

It looks very much like extension method however you can spice it with inheritance:

class Greeter<T>
{
}

trait Greeter<T> : ISay
  where T : ISay
{
  void hello(T &t) 
  {
    t.say();
  }
  // traits support polymorphism
  override void say()
  {
    println("just saying");
  }
}

Depending which type you pass when constructing Greeter it will inherit from ISay or not.

At this point there is a little zoo in Skila with similar features — protocols and interfaces, traits and extensions (upcoming) — and I hope some of them will be merged. Simplicity matters.

Tagged , ,

Associated reference

I’ve just made up the term “associated reference”. While many features in Skila are simply borrowed from other languages I have never encountered this one before. And it looks bad — it is complex, fragile and I would gladly get rid of it, but I can’t, and here is why…

Consider such case as passing few arguments to C# variadic function. Variadic parameters in C# are arrays, arrays are allocated on heap, and this is inefficient. Skila has limited support for C++ like references, so in Skila we create array on stack and pass a reference to it to the function. It is efficient. Step one sounds good?

Step two — we are inside the function and we would like to iterate over the variadic parameter. We need an iterator for it, and in turn iterator needs a collection to iterate over. Passing the collection as a value would mean copying collection, passing it as a pointer is desirable, but in a case like the current one we don’t have a pointer, just a reference. So the reference is the only option. The logic looks sound.

Step three — we need to store that reference inside iterator instance. And this makes the iterator a special kind of type — an associated reference.

Once introduced, all hell with validation breaks loose. For example associated reference type can have only single field with reference, it can have only one constructor with single parameter, which is of reference type. The associated reference type can be passed only by reference and its lifetime has to be limited to the life of the “seed” instance.

Fragile, complex, first feature which does not seem right.

And yet the only alternative I see is redesigning the concept of a stack by allowing to partially unwind it (to preserve referenced instances). At first glance it looks even worse than associated reference so I stick with it for a while.

Tagged , , ,

Memory chunks and inheriting enums

Type Chunk type represents a chunk of memory — either on stack or on heap. The latter use is pretty obvious, such types as Array or String will use it. The placement on stack is crucial for making variadic functions efficient — take for example C#, it supports variadic functions, but since arrays are allocated on heap, the comfort of using variadic functions comes at such price that it is not uncommon to see several overloads with unrolled parameters just to avoid variadic parameters.

Having Chunk allows efficient implementation of variadic functions — there is no memory allocation, just extra internal “parameter” (number of elements).

Unrelated to memory, another new feature is ability to inherit enums. From time to time I was annoyed I had to repeat enums in C# just to add few new entries (and remember to keep the same common values), so in Skila you simply derive one enum type from another and share common values. I am not sure if the inheritance is the correct mechanism here, because substitution rules are reversed for enums (you can pass base value when the derived type is required), but since it works I leave it as it is.

Ahead of me is reimplementing old rules of method-property derivation.

Tagged , , , ,

Recursive and ancestor calls — again

I‘ve just brought self and super calls back — more about these features in my previous post about them.

I am just not happy how super is implemented currently — I was forced to add initial stage of scanning all the signatures of types and functions in order to compute function derivation tables. So when I hit the body of the function — during the second stage of processing — I am able to bind super properly.

Tagged , ,

Type unions and intersections

This is something I wanted to do from the first days of Skila, however the engine in Skila-1 was such a patchwork that it died before it was possible. Skila-3 finally has both kinds of sets:

let y *Submarine & *Plane = ••• 
let x *Submarine | *Plane = •••

The first line declares an intersection — we are making here some really super vehicle which can fly, dive and speedUp, because it has all the members of given element types.

On the other hand, the second line builds somewhat limited vehicle using a type union — we can only speedUp, because it has only common members, which are present in each of the given element type.

Is it practical in real life or is it just a fancy academia stuff? The former, the lack of set types in C# is killing me — I can mitigate the problem a little when I am getting data:

void loan<V>(V vehicle) 
  where V : Submarine, Plane

which gives me an intersection, but I am toasted when it comes to the output:

V create<V>() 
  where V : Submarine, Plane

It breaks the responsibility rule, because caller of create has to specify concrete type and inside create we have no chance to really create it (except for trivial case with default constructor).

And I have to cope with it all the time when writing wrappers for devices — I take them specifying their capabilities as constraints but I am unable to specify the capabilities of what I create (unless I am willing to create ton of interfaces — TelescopeWithThis, TelescopeWithThat).

Tagged , , ,

Prototypes and “has” constraint

I am really glad I went with interpreter — it allows me to check ideas much faster than before, when I was playing with PHP transpiler.

I added support for prototypes (as in Go) and an option for regular interfaces to turn them into prototypes as well — so you can type substitution matching or structural matching:

prototype PHost
{
  string say();
}

interface IHost
{
  string say();
}

struct Host : IHost
{
  public override string say() => "hello";
}

let h1 *IHost = new Host();
h1.say();
let h2 *PHost = new Host();
h2.say();

The first declaration and call work because the types are matched as in C# — Host is derived from IHost and substitution is possible. In the second case it is also possible but not because Host is derived from PHost (it is not), but because all the methods PHost requests are defined in Host.

I am wondering about adding option to limit type implementation (class or struct) substitution. Say you have class “Parent” and derived from it class “Child”. You could pass *Child for *Child or *Parent for *Parent, but not *Child for *Parent. For full substitution you would have to go with interfaces/prototypes and this in turn would promote using them.

Supporting the notion of the duck typing already paid off — it was fairly easy to add has constraints for templates:

void welcome<T>(actor *T)
  where T has string say();
{
  actor.say();
}

When there is any has constraint present, compiler turns internally the type parameter into a prototype and the rest is already written.

In the example above I pass the pointer simply because I didn‘t yet write templates properly and they don’t work with values. Maybe next weeek…

Tagged , ,