Pyro

A dynamically-typed, garbage-collected scripting language.

Version 0.21.0

Functions


Function Definitions

Function definitions look like this:

def add(a, b) {
    return a + b;
}

assert add(1, 2) == 3;

If you don't explicitly return a value from a function, its return value is null.

Functions As Values

Functions are first-class citizens in Pyro, meaning you can assign them to variables and pass them around just like any other value, e.g.

def add(a, b) {
    return a + b;
}

var foo = add;
assert foo(3, 4) == 7;

Anonymous Functions

You can declare and use functions anonymously, e.g.

var add = def(a, b) {
    return a + b;
};

assert add(5, 6) == 11;

Closures

Pyro has full support for closures. An inner function declared inside an outer function can capture the outer function's local variables, including its parameters, e.g.

def make_adder(increment) {
    def adder(num) {
        return num + increment;
    }
    return adder;
}

var adds_one = make_adder(1);
assert adds_one(0) == 1;
assert adds_one(1) == 2;

var adds_two = make_adder(2);
assert adds_two(0) == 2;
assert adds_two(1) == 3;

Functions can also capture global variables, e.g.

var suffix = "_end";

def make_closure() {
    return def(string) {
        return string + suffix;
    };
}

var adds_suffix = make_closure();
assert adds_suffix("foo") == "foo_end";

suffix = "_new_end";
assert adds_suffix("bar") == "bar_new_end";

Default Argument Values

You can specify default values for trailing arguments, e.g.

def func(arg1, arg2 = 123, arg3 = "foo" + "bar") {
    return arg1, arg2, arg3;
}

assert func(111) == (111, 123, "foobar");
assert func(111, 222) == (111, 222, "foobar");
assert func(111, 222, 333) == (111, 222, 333);

Default-value expressions are evaluated when the function is called and can capture local and global variables from the surrounding scope.1

Variadic Functions

A variadic function is a function that accepts a variable number of arguments.

To define a variadic function, prefix its final parameter with a *, e.g.

def variadic_func(arg1, arg2, *vargs) {
    return vargs;
}

assert variadic_func(1, 2) == $tup();
assert variadic_func(1, 2, 3) == $tup(3);
assert variadic_func(1, 2, 3, 4) == $tup(3, 4);

The variadic arguments are available inside the function as a tuple.

Unpacking Arguments

When calling a function, you can 'unpack' the final argument using a * if it's a tuple or vector, e.g.

var numbers = [1, 2, 3];

var sum1 = add(*numbers);           # add(1, 2, 3)
var sum2 = add(123, 456, *numbers); # add(123, 456, 1, 2, 3)

Trailing Commas

Trailing commas are allowed in function definitions, e.g.

def add_args(
    arg1,
    arg2,
    arg3,
) {
    return arg1 + arg2 + arg3;
}

Trailing commas are also allowed in function calls, e.g

var result = add_args(
    "foo",
    "bar",
    "baz",
);

assert result == "foobarbaz";

Multiple Return Values

Functions can return multiple values, e.g.

def get_values() {
    return 123, 456;
}

assert get_values() == (123, 456);

This is syntactic sugar for returning a tuple — i.e. it's equivalent to:

def get_values() {
    return (123, 456);
}

You can unpack a tuple returned by a function in a var declaration using (), e.g.

var (foo, bar) = get_values();
assert foo == 123;
assert bar == 456;

Notes

1

If you're familiar with Python, you'll recognise this as a classic footgun:

def fn(arg = []):
    ...

The problem is that, in Python, every call to this fn function will share a single instance of the default list argument, [].

In spite of its many similarities to Python, Pyro doesn't share this footgun. Take the similar-looking Pyro function:

def fn(arg = []) {
    ...
}

Every call to this Pyro function gets its own unique instance of the default vector argument, [].

This is because, in Python, default argument expressions are evaluated once when the function itself is evaluated. In Pyro, default argument expressions are evaluated every time the function is called.