Category Archives: Skila features

Performance crisis

The last days were really tough for me because everything started to fall apart — compiler was way too slow to experiment with, programs written in Skila were too slow to run and allocated too much memory. To build DOM tree out of 1MB web page — single web page is taking nowadays over one million bytes? that’s insane — I needed over 0.5GB of memory.

Without solving those issues I would have to stop development of Skila, using other backend for speed and memory has no sense, since I am still experimenting with language features. So I had to blend in with smarter internal wiring.

First I dropped basic types from the backend — Char, Int and Real. They were just wrappers and because of their ubiquity they took a lot of extra space in total. Currently I use native PHP values and redirect them to appropriate static classes — when I know in advance I have for example Int type in hand I call static method for it. When I don’t know what I have I call small dispatcher which checks the types in runtime and makes appropriate call. Luckily PHP makes such task trivial. I saved around 60% memory with this move.

It was promising but not enough — I still had the bottleneck and its name was String (another commonly used type) which was implemented as Skila Array of Chars. When you split 210KB string into an PHP array of characters (since PHP does not have Char type it is an array of strings really) you will get 14MB array — in other words single character takes around 60 bytes. You can reduce the memory a little by converting an array to splFixedArray, but there is no enough gain here (not mentioning time required for all back and forth conversions). So I decided to simply wrap the native string with my type and rewrite all String methods. That move was big time winner — from initial 0.5GB the memory requirement dropped to 7MB and the speed is close to native PHP code, because now in fact it is almost native PHP code.

Currently I am going to make two micro optimizations — postponing parameter default initialization and adding inline functions. Both for sole purpose — speeding up assert execution.

With compiler I was not so successful — I use rich AST with pointers to parent’s node. It is really comfortable to work with such tree (and worse — I got used to it) but there is penalty in performance, you have to clone nodes whenever you would like to share some subtree. I don’t know how I will get rid of it completely, nevertheless the diet is already implemented with positive effect — compile time was reduced by 50%.

A lot of work ahead of me, but at least I managed to get out of trouble — I can continue my journey…

Tagged , ,

No point for being tidy

After some consideration I dropped checking formatting of the code. My reasoning was this — say you have 1000 errors just because you didn’t indent the code correctly. Will you fix it by hand or automatically? I guess you choose the latter, but in such case what is the point of checking the formatting in the first place? To duplicate testing correctness of your IDE?

There is a second reason — you come up with some great idea, you start coding, you compile and now not only you have to fix some technical errors but also you need to clean up the indentations. It does not sound very helpful, when you have great idea, you should focus on that idea, nothing else matters. So checking formatting is not solving the problem, and if you are not the solution of the problem, you are part of the problem.

It does not mean I am supporting messed up code, I am saying it is job of IDE which should (probably) reformat the code on final save, so when you open your code next time, you will see all the cleaning is already done.

Tagged

Introducing nearby exceptions

While working on this post, I discovered that Swift uses already very similar approach of handling the errors.

The implementation is cheapest I could think of, I am aware of the problems with backend, but enough with excuses — it works, so I will be able to play with it and test if the model is useful or not.

To keep things short let’s start with C# approach which is used in Skila as well:

Errors as exceptions

You access an array with index out of its boundary. You divide by zero. In cases like those you throw an exception to indicate the error in computation.

Errors as values

Your common sense, practice, intuition, tells you that for some computations throwing an exception would be too harsh, so instead you return magic value instead to indicate “not a value” value. Probably the best example is String.indexOf — for such purpose Skila has type Option<T> and special value null (it is not null you know from C# — it is like Scala none or null in Swift or Kotlin).

Ok, that’s it.

Wait, we missed the magic ingredient which makes exceptions fun to work with! Consider accessing the dictionary — to provide similar API as with arrays, when the key is not found the exception is thrown, it happens in C# and Skila as well. But it would be useful to be able to try to fetch the value — in C# you have to add another function to the class (TryGetValue in this case). In Skila you convert one mode (error as exception) to the other one (error as value):

let dict = {String:Int}();
let val ?Int = dict["hi"] $;  

Let’s focus on the second line — when we access the dictionary (it is empty) the exception is thrown from the indexer getter. That exception in Skila is special — it is nearby exception (because it is thrown in nearby, or next if you like, function we just called). Then there is dollar — $ — character which works as an exception trap. Only nearby exceptions can be trapped (the others go through) — and if this happens, they are converted to null value.

You don’t have to write duplicated code as in C#, you simply convert errors as you like — go from null to exception, or from exception to null. Both conversions are expressions and they are pretty accurate:

def thrower() String => throw Exception();

let dict = {String:Int}();
let val ?Int = dict[thrower()] $;  

Can you tell what will happen? The program will crash — that is because we trapped exception coming from dictionary indexer, not a thrower.

Why not trap all exceptions? Because I would like to distinguish between three outcomes in total — we looked and we found the key, we looked and we didn’t find the key (i.e. we are sure the key is not there), we crashed while looking (because, say, our comparison function is buggy), so we cannot tell if the key exists or not.

Maybe it is an overkill but after a day of playing with exception trap I felt I miss one more feature — consider reading an ini file and then adding key and value pair to properties dictionary:

properties!add(key,value);

When the key already exists we will get exception with a message and stack trace. However we won’t get plenty of information about the context of the problem, like the filename or line number. Sure, we can debug, but we might add those information right away:

properties!add(key,value) && "File ${filename}, line ${line}.";

The mechanism is old as exceptions — chaining. You can chain an exception or a string (Skila will create basic Exception for you). Since the new exception is created it is equivalent of explicit throw, i.e. for the outer function such chained exception will be a nearby exception.

And to just describe all the tools required for the job — there has to be a way to close the distance of the exception, otherwise assert and fail would be useless:

def [key K] V
set:
  •••
  fail("Key not found.");
end

Your code calls dictionary indexer, dictionary indexer calls fail, fail calls assert, assert throws an exception. So for sure such far placed throw will not be considered as “nearby” from your code perspective. Unless we tamper with the exception distance counter:

def assert(•••) Void
  •••
  throw Exception(•••) &&>>;
end

def fail(•••) Void
  assert(•••) &&>>;
end

This cryptic code &&>> tells compiler to pass an exception further. Effectively when you write assert or fail the exception which is thrown thinks it originated from where those functions were called.

Tagged , ,

It

Mundane tasks are for computers, not me — I was bored with all the typing class Iterator<T> ••• and then somewhere later ••• Iterator<T>. It is current class, why do I have to repeat myself? So I added alias to current (compile time) type — It.

Tagged ,

Arbitrary quotations

While working on html code I realized (for the n-th time) that having just verbatim and non-verbatim strings is way too little. Ruby did it right, so I simply copied proven solution:

let s = %s<<p>quotation counts "parentheses"</p>>;

In the process I reverted the syntax for characters literals back to C#:

let c = 'b';

at the expense of adding backtick as meta switch. In Ada apostrophe serves dual role but I don’t see how to make it in Skila grammar:

••• > ' •••

would be ambiguous (accessing meta information about generic type or comparing characters).

Tagged , , , , , ,

No more lies about static fields

When you declare a static field in C# it is not a field per se — it is a tiny getter which initializes the field on first read. I don’t like this approach because it misinforms the user — user wants a direct access but instead she or he gets a wrapper function. Small lie but still a lie. Initializing all static fields eagerly, even unused, is also not a good idea. So I tried to combine those two in a meaningful way…

Static fields can be initialized only with basic values and the initialization is done eagerly (plain old Int static field with initial value 100 is fine). Every other static field is forbidden.

For other types (and complex initial values) properties come to the rescue — properties are initialized lazily with added guards. The message is clear and the safety is guaranteed, in case of circular initialization you will get nice crash (I am not joking, on various occasions I noticed .Net 4.5 keeps working with silently inserted nulls).

Those changes effectively killed static variables so I made temporary exception in sake of testing them — any Int expression is treated as basic value. Another temporary leftovers are static constructors — I am not sure what to do with them, they will be limited to linear initialization or completely removed.

Full solution will take more time — sure bet are lazy types. With lazy types the notion for user is straightforward thus they could be used for all static fields. The other solution could be allowing function and namespace properties.

Tagged , , , , ,

Two faces of extensions

Before we begin and I forget — if you place any extension at the same file the type is defined you have full access to all private members of it. Phew, now we can begin…

External extensions

The idea of external extensions are taken from C# — I simply love it. The extension keyword and grouping is taken from Swift.

Those are pure syntactic sugar, there is nothing you couldn’t write by yourself using non-extension part of the language.

Micro extensions

Let’s say you have collection of collection of some elements, the common task is to flatten the data. You cannot add such method to Sequence type, but external method just fits right in:

extension def flatten<E>(this ~~E) ~E
  ••• 
end

Three things to notice:

  1. the function is placed directly in (any) namespace,
  2. the modifier is extension,
  3. there is explicit this parameter.

Such extension can be used as a function or as a method:

_ = flatten([[1,2],[3,4]]);
_ = [[1,2],[3,4]].flatten();

Extension types

Here we group the methods and properties into extension type — I save better example for later so let’s use absurd one:

extension class Real
  def square Real get => this*this;
  def cube Real get => square*this;
end

We didn’t change the source of the type. We just added new properties on the side:

_ = 4.5.square;
_ = 1.7.cube;

Please note in case of extension types, we don’t add explicitly this parameter and we can only call methods as, well, methods.

Internal extensions

I didn’t have time (and a book) to check in detail how much Skila differs from Swift but it seems there was a different need that drove the idea of this extension, thus Skila extensions are more limited. Currently I am reading about Objective-C — and surprise, surprise — I noticed it supports extensions.

Unlike C++ template specializations you cannot provide customized version of a given method present in a template. You can only add new methods (or properties) which do not exist in a template being extended.

Consider two sequences of some elements. Would it be useful to test whether those sequences are equal?

if [a,b] == [c,d] then
  ••• 

It didn’t work, because Sequence couldn’t blindly assume its elements support equality test. With our new tool we can add appropriate method. Our first try:

extension interface Sequence<T>
  where T refines Equatable;

  def ==(cmp ~T) Bool 
    ••• 
  end
end

With above external extension we can test equality of two sequences in safe manner (this is not true in C# which provides unsafe Equals — pretty much the same as in dynamic languages).

But is it really all? No — despite we can perform the test, we didn’t change the Sequence type so we cannot compare a sequence of a sequences of Equatable elements, because the sequence of Equatable elements does not inherit Equatable itself:

%% does not work with above extension
if [[a,b],[i,j]] == [[c,d],[k,l]] then
  ••• 

We need internal extension (specialization):

extension interface Sequence<T> refines Equatable
  where T refines Equatable;

  refines def ==(cmp ~T) Bool 
    ••• 
  end
end

What does it mean? A general Sequence type does not inherit from Equatable interface (because it cannot), but once we drop something that is Equatable inside Sequence type, we get type specialization which does inherit from Equatable.

There is more — a type which inherits from Sequence (like !Array) also behaves in the same way, its general version is not Equatable, but if you have let’s say array of numbers (which are Equatable) entire array becomes Equatable — think of it as one specialization inherits from another specialization.

Specializations can be combined — imaginary example, if we wrote specialization for Sequence to inherit from X when elements inherit from X, and in same manner we did with type Y what happens if we have Sequence of elements which derive from X and Y at the same time? We will get Sequence type inheriting from X and Y.

The bill? You have to place internal extensions in the same file as type being extended. Also you cannot extend non-template type (it does not make sense in Skila perspective, because there would be no way to discriminate such extension).

Tagged ,

Method cascading

I finally have a tool from fluent interfaces toolbox I needed. Usually you have some type of container with a method:

def !add(elem T) Void
  %% adding element
end

Wait, or should be it like this:

def !add(elem T) 'Self
  %% adding element
  return this;
end

The second form allows method chaining, but on the other hand there is overhead of result passing no matter if anyone uses it or not. And what about returning boolean flag indicating whether adding was successful or not. In such case we couldn’t return the container.

Method cascading answers some of those questions, we don’t need a version which cooperates with us because we isolate the object and reuse it:

coll |> !add(a) |> !add(b);

b is not added to the outcome of adding a (it would be a method chaining), but to the isolated object on the left — coll. Of course sometimes method cascading can have the same effect as method chaining, but in general they are two distinct tools.

The above code is equivalent to:

coll!add(a);
coll!add(b);

You can find more about method cascading at Wikipedia.

I took the symbols of the pipe operator — but not the meaning — from Elm (if I remember correctly I first saw it in Seven More Languages in Seven Weeks by Bruce Tate et al) and initially I thought it was function cascading operator which is not.

Tagged ,

Blocks as expressions

The irony is the moment I finished this feature I realized it is not enough — I need also a tracking system which would allow to postpone initialization.

But at least when you need to do some precomputation and then initialize variables you can use entire block as initialization value:

let (yyyy,mm,dd)
  = * do
        let parts = s.split("-");
        // the outcome of the entire do-end block
        parts ++ "".copy((3-parts.count()));
      end

It is better than nothing but I would prefer to write:

let yyyy,mm,dd Int; // postponed initialization
do
  let parts = s.split("-");
  // initialization
  (yyyy,mm,dd) = *(parts ++ "".copy((3-parts.count())));
end

In both cases temporary variable parts does not leak, but I am more used to the latter form. On the other hand only the former will work with loops, unlike in Scala which returns just unit (void in Skila) I am going to add some muscles to them.

Tagged , ,

Rich loops just got richer

The only missing piece in rich loops was building continuation nested loops — here the old C-like loops were obviously winning. In order to move closer to the podium I made few improvements, the first one — I cannot decide if this is an ugly hack or well designed concept.

I introduced iterator providers. Iterator provider is an object which can provide an iterator (yes, a groundbreaking approach). Usually you can think of a sequence as an iterator provider but there is another one obvious candidate — an iterator itself. It just returns copy of this. Such addition allows loops to work with iterators directly:

let iter = some_coll.getIterator();
for elem in iter do
  •••  
end

The second improvement is allowing to iterate over non existing data — in rigid mathematical sense an empty set is a not equal to no set, however so far I don’t see any danger with treating them here the same:

let none ?[Int] = null;
for elem in none do
  •••  
end

The third improvement is the smallest, it just supports method chaining. Advancing iterator instead of returning true/false returns an optional iterator. On a successful iteration — the iterator itself, on a failure — null. This part will be changed as soon as I implement reference passing as it was done in Smalltalk.

Those three changes combined let you write nested loops without any effort:

let coll = [1,2,3];
top: for x in coll do
  for y in top.iterator do
    System.Terminal.stdOut.writeLine(x,",",y);  
  end
end

It will write such pairs — (1,1), (1,2), (1,3), (2,2), (2,3), (3,3). Please note there are pairs where y is equal to x. When this is undesired start nested loop from the next element in collection — i.e. advance the iterator.

let coll = [1,2,3];
top: for x in coll do
  for y in top.iterator!next() do
    System.Terminal.stdOut.writeLine(x,",",y);  
  end
end

You should see — (1,2), (1,3), (2,3).

The iterator attribute of the top control is a copy of an iterator used internally by compiler, so you can alter it any way you want, it won’t harm the outer or inner loop.

Except for optimizing the internal wiring of rich loops I am done here, the plan is 100% completed.

Tagged