Saffire january 2013 update

Warning: This blogpost has been posted over two years ago. That is a long time in development-world! The story here may not be relevant, complete or secure. Code might not be complete or obsoleted, and even my current vision might have (completely) changed on the subject. So please do read further, but use it with caution.
Posted on 26 Jan 2013
Tagged with: [ saffire ]  [ varags

Most development languages will have some kind of printf() functionality. It takes a string, and can have optional arguments, depending on the placeholders you have set inside your string.

printf("Hello %s. You are %d years old", name, 33);

The %s and %d will be filled with respectively the variable “name”, and the integer 33. However, how do we declare such printf() method since we don’t know how many arguments we can pass. One solution is to pass all arguments inside a single datastructure:

method printf(string format, list arguments)

which would work since we always pass 2 arguments: the format and a list. However, calling such method would be cumbersome. In pseudocode:

args = array("hello", 33)
printf("hello %s. You are %d years old", args);

In Saffire, we support method overloading, which means that we can define the same method, but with different arguments:

public method printf(string format) { .. }
public method printf(string format, arg1) { .. }
public method printf(string format, arg1, arg2) { .. }
public method printf(string format, arg1, arg2, arg3) { .. }

But again, not really a great way to solve the problem. We need to define a printf() method for EVERY possible argument-count possible. If someone wants to pass 25 arguments, we need to have a method that accepts 25 arguments. Not really a viable solution.

Variable Arguments

Luckily, most languages solve this problem by supporting variable arguments. This means that the argumentlist of a function can have undetermined amount of arguments. Inside the function body, those arguments can be fetched easily by some additional code. The C language uses the va_* macro’s for this. PHP has got func_get_arg(s) and func_num_args and Python uses *args and **kwargs.

A PHP example:

function foo() {
    print func_num_args();
}

foo("bar", "baz"); // Outputs 2
foo("foo"); // Outputs 1
foo(); // Output 0

Now there are basically three downsides of this way of dealing with varargs (at least in PHP):

  1. You cannot see by the function definition if the function that it supports variable arguments
  2. The func_get_arg(s) functions always start at parameter 0, even if you define mandatory arguments in the definition.
  3. Variable arguments cannot be passed on to lower functions

Variable arguments in Saffire

In Saffire, we use variable arguments a bit different. First of all, you MUST define in the argumentlist that you will be using variable arguments:

public method foo(string format, ... args);

This will tell Saffire that all arguments AFTER the first, are variable. You can call this method with either 1 argument (only the format), or multiple. The rest of the arguments will be filled into the “args”.

This “args” is just a simple list-datastructure that holds all the (variable) arguments. Because its a list, you can just use it as a normal list:

public method foo(string format, ... args) {
    io.printf("Format is: '%s' and the the number of variable arguments: %d\n", args.length());
}

Just as any list, you can splice, delete, add or simple mutate elements inside the list. Usage is easy too:

public method foo(string format, ... args) {
    for (i=0; i!=args.length; i++) {
        io.printf("Argument %d: %s\n", i, args[i]);
        }
}

Because format is filled in the normal way, args[0] will always point to the first variable argument, so we solved two PHP problems. The third PHP problem is a bit more tricky, and is just as tricky in C. Sometimes we just want to pass all the variable arguments to a lower function.

public method uppercaseFoo(string format, ... args) {
        self.foo(format.upper(), args);
}

public method foo(string format, ... args) {
        io.print(format, " ", args.length());
}

Never mind the really bad example. However, if we want to run the example, we have a problem.

self.foo("hello", 1, 2, 3); // Outputs: hello 3
self.uppercaseFoo("hello", 1, 2, 3); // Outputs HELLO 1

This looks strange: why would uppercase foo outputs the correct format (the uppercased version), but only 1 variable argument? The reason is simple. Remember that the args are actually “converted” to a list data structure? When we call the self.foo() method inside the uppercaseFoo() method, we actually pass on only one argument: the list.

But if we want to pass the list as separate arguments to the self.foo() method, we must tell it so. We do this by adding ... (called ellipsis) in front of the list during the method call:

public method uppercaseFoo(string format, ... args) {
      self.foo(format.upper(), ... args);
}

When we call uppercaseFoo(), we actually get the correct result. The ellipsis in the method call lets Saffire know that the next variable is a list, and that all elements in that list must be passed separately.

Because of the design of Saffire, this opens up lots of possibilities  All that it needs is a list. How it gets there, does not matter. This means that the following would work too:

args = list["foo", "bar", "baz"];
self.foo("hello world", ... args);

and:

self.foo("hello world", ... list[1,2,3]);

or even:

self.foo("hello world", ... self.functionThatReturnsAList());

But since the ellipsis inside a call just expands arguments, it means it can be used to passed non variable arguments as well:

self.foo(... args);   // Will work ONLY when args[0] is a string.

More complex examples to demonstrate its power:

public method foo(string arg1, numerical arg2, ... vararg) { 
    // do something 
}

public method bar() {
    args = list["foo", 1234, "a", "b", "c"];

    // arg1="str", arg2=9, vararg[0] = "foo", vararg[1] = 1234 etc..
    self.foo("str", 9);     
    // arg1="foo", arg2=1234, vararg[0] = "a", vararg[1] = "b" etc..
    self.foo(... args);  
}

Basically, the … just expands a list as arguments, and a method call just “eats” arguments. It’s also worth mentioning that you can use the ellipsis to expand arguments, even when a function does not support variable arguments:

public method foo(string arg1, numerical arg2) {  
    // do something  
}  
public method bar() {  
    self.foo(... list["str", 1234));  
}

Off course, it means that your list must consist of 2 elements, where the first argument MUST be a string and the second MUST be a numerical. It might not make sense in most cases to use argument expansion for non-variable argument methods, but there might be use-cases. Saffire won’t mind :)

Updates this month:

So this month we did lots of good work. We are almost ready on getting the exception-handling inside Saffire, so hopefully I can spend some time on writing about it next month. This also means we can use try/catch/finally blocks. Just like python, there will be a default handler to catch uncaught exceptions, which you can override yourself (think of this as a try/catch block around your application).

Also, we’ve got many of the OO system running so creating and using classes and instances are functional. There were some issues when internal code wants to call external (or overridden) methods. This might happen where casting is done implicitly, which happens during comparisons and during io.print(), so you don’t have to cast everything to strings yourself. Luckily, we managed to fix it and this works perfectly.

Because variable arguments are working, we could finally create our first Saffire modules: the IO class. This module has got some wrappers to the internal modules so things like "import io" finally works without importing the "::_sfl::io" module directly.

Februari will be a slow month probably. Because of work, but mostly of the birth of our first child! Meaning not much will probably be done, but hopefully we can try and get data-structure indexing working (so we can do: list[5] instead of list.get(5)). Also we might start and tackle iterators, so we can get foreach working.