Monthly Archives: July 2015

Two sides of variadic parameters

Until now variadic parameters seemed obvious for me. But consider such code:

stdOut.writeLine("hello","world");
list.append('a','b','c');
let arr = Array<Int>(data:1,2,3);

Looks fine (Skila has also shorter syntax for arrays, similar to Swift). But since all three methods are defined with variadic parameter let’s remove arguments:

stdOut.writeLine();
list.append();
let arr = Array<Int>();

Still technically valid, yet there is something off with the second call — what is the point of appending nothing to the list? I noticed it, however it looked like a corner case at the time, something not perfect but not worth worrying about so I moved on.

But recently I had trouble with overriding methods (Skila does not have explicit overriding, yet). For example with existing constructor for `Array`:

def init(data: T ...)
  // body of the method
end

it is impossible to define default parameterless constructor. The straw was broken — I had to find out the nature of the problem. As it appeared, it is the dual perspective of variadic parameter. One side is what we pass, and the second side is what we get. If we got no data at all from parameter, does it mean user passed empty “bag” of data, or no data at all?

Obviously there are two notions and both need to be reflected in semantics of the language because both are useful — Skila currently supports such definitions as:

// can be called with no arguments
def writeLine<T>(s T ?...) // code goes on
// has to be called with at least one argument
def append(elem T ...) // code goes on

Please notice you can still call `append` with argument having no data in it, but there has to be something between parentheses of the call.

And as a reminder — this feature is not a replacement of variadic limits. For example with only limits, you cannot express `append` as above. Compare:

def append(elem T 1...) 

Such code works the same for explicit arguments, but not for splatted (unpacked) collections:

let a = 5;
let xx = Array<Int>(data:1,2,3);
let yy = Array<Int>();
list.append(a);
list.append(xx/...); // splat operator
list.append(yy/...);

On the last call you will get exception (in run time), because `append` with limits requires on the callee side at least one element of data. Such definition of `append` is not robust for real use.

Advertisement
Tagged , ,

Self type and pinned spiral

It is amazing how different ideas can all of the sudden click and create one coherent blueprint for a cool feature. Yes, I know, I should be doing generator — but this is exactly where the problem began.

Here is how it went — I was implementing rich `Iterable` interface and stumbled on `concat` method. On the first glance it is obvious — it takes another `Iterable` and also returns `Iterable`, no problem. But think about using it — when you have some arbitrary `Iterable` it works as expected, but if you have for example `String`… Do you really expect to concatenate two strings and get some `Iterable` in result?

OK, so let’s make a correction ¹ — the outcome should be `Self`. It will work perfectly with `String`, we can also define `prepend` and `append`. But… wait a second, `Set` is `Iterable` as well, right? How can you prepend anything to a set and get a set back? If you don’t see a problem — you can add something to set, but not concatenate sets, or append anything to it, because except for one special case, set is not a sequence (there is no order in a set).

I found a solution, it waits right now for implementation (I needed a break from coding, thus this post) — `Set` is not an `Iterable`, it can be treated as one (thanks to implicit conversion) — subtle difference, but making things consistent. You can call `append` on `String` and get back a `String` and at the same time you can `append` on `Set` and get an `Iterable`, because it will be silently converted to this type on call.

Implicit conversions were easy — they are just a parameterless methods overloaded on result type. In Skila with “have to read the result of the function” feature it simply fitted right in.

One thing was still missing — how can you ensure that regular method returns `Self` type? You can track whether the returned value is really pure `Self`, but sometimes you don’t work with `Self` type in the first place. The only alternative would be relying on the fact that every descendant type will override such method and produce its own type, making effectively given method a factory of `Self`. And this concept was already present in Skila — `pinned` regular methods waited long for such cause, but now it is done. So either you write one general method with `Self` or you will guarantee with `pinned` every type will have its own implementation.


¹ If I am not mistaken, Scala solves similar kind of problems with what-can-build-what types. This part seems too convoluted for me so I even didn’t dig deeper.

Tagged , , , ,