# Function

Functions are a very important element of ABS, as they are the core of userland customizations.

A function is declared with the following syntax:

f(x, y) {
    x + y
}

As you might notice, the return statement is implicit. You can make it explicit, but we advise not to, in order to keep your code as concise as possible:

f(x, y) {
    return x + y
}

Most languages use a more "explicit" identifier for functions (such as function or func), but ABS favors f for 2 main reasons:

  • brevity
  • resembles the standard mathematical notation everyone is used to (x ↦ f(x))

Functions can be passed as arguments to other functions:

[1, 2, 3].map(f(x){ x + 1}) # [2, 3, 4]

and they can be assigned to variables as well:

func = f(x){ x + 1}
[1, 2, 3].map(func) # [2, 3, 4]

Scoping is an important topic to cover when dealing with functions:

a = 10
func = f(x){ x + a }

f(1) # 11
a = 20
f(1) # 21

ABS supports closures just like mainstream languages:

func = f(x) {
    f(y) {
        x + 1
    }
}

# can also be expressed as

func = f(x) {
    return f(y) {
        return x + 1
    }
}

# Named functions

You can create named functions by specifying an identifier after the f keyword:

f greet(name) {
    echo("Hello $name!")
}

greet(`whoami`) # "Hello root!"

As an alternative, you can manually assign a function declaration to a variable, though this is not the recommended approach:

greet = f (name) {
    echo("Hello $name!")
}

greet(`whoami`) # "Hello root!"

Named functions are the basis of decorators.

# Optional parameters

Functions must be called with the right number of arguments:

f greet(name, greeting) {
    echo("$greeting $name!")
}
greet("user")
# ERROR: argument greeting to function f greet(name, greeting) {echo($greeting $name!)} is missing, and doesn't have a default value
# 	[1:1]	greet("user")

but note that you could make a parameter optional by specifying its default value:

f greet(name, greeting = "hello") {
    echo("$greeting $name!")
}
greet("user") # hello user!
greet("user", "hola") # hola user!

A default value can be any expression (doesn't have to be a literal):

f test(x = 1){x}; test() # 1
f test(x = "test".split("")){x}; test() # ["t", "e", "s", "t"]
f test(x = {}){x}; test() # {}
y = 100; f test(x = y){x}; test() # 100
x = 100; f test(x = x){x}; test() # 100
x = 100; f test(x = x){x}; test(1) # 1

Note that mandatory arguments always need to be declared before optional ones:

f(x = null, y){}
# parser errors:
# 	found mandatory parameter after optional one
# 	[1:13]	f(x = null, y){}

# Accessing function arguments

Functions can receive a dynamic number of arguments, and arguments can be "packed" through the special ... variable:

f sum_numbers() {
    s = 0
    for x in ... {
        s += x
    }

    return s
}

sum_numbers(1) # 1
sum_numbers(1, 2, 3) # 6

... is a special variable that acts like an array, so you can loop and slice it however you want:

f first_arg() {
    if ....len() > 0 {
        return ...[0]
    }

    return "No first arg"
}

first_arg() # "No first arg"
first_arg(1) # 1

When you pass ... directly to a function, it will be unpacked:

f echo_wrapper() {
    echo(...)
}

echo_wrapper("hello %s", "root") # "hello root"

and you can add additional arguments as well:

f echo_wrapper() {
    echo(..., "root")
}

echo_wrapper("hello %s %s", "sir") # "hello sir root"

# Supported functions

# call(args)

Calls a function with the given arguments:

doubler = f(x) { x * 2 }
doubler.call([10]) # 20

# str()

Returns the string representation of the function:

f(x){}.str()
# f(x) {
#
# }