Function Type

Functions are the cornerstone of any programming language allowing users to abstract and parameterize parts of their code.

Functions in Idio are first class values and can and, indeed, are commonly passed as arguments or the value returned by another function.

There are two basic kinds of functions, primitives and closures, and two function-like behaviours called special forms and templates.

Primitives

Primitives form the bedrock of the language. They are written in C and access to them is exposed by the VM such that they can be invoked and their results returned like any user-defined function.

Writing functions in C to behave like Idio processing Idio parameters can be tiresome so generally primitives are written for actions that user-defined functions cannot do like alter memory. Hence all of the value-manipulating functions are primitives as they have to poke around in the underlying implementation to return, say, the size of an array – which you might reasonably guess is going to be a size_t in a C struct – and return it as an Idio integer.

Otherwise someone might write a primitive:

  • where speed is considered important

  • as an interface to a library of support functions, as with libc, for example.

Closures

Closures are user-defined functions and allow the convenient abstraction of sections of code.

Normally you would define a closure with the special form define:

define (foo a b) {
  a + b
}

which creates a function called foo which takes two parameters, a and b, and performs some addition on them.

Alternatively you can create an anonymous function value with the special form function:

function (a b) {
  a + b
}

which seems to do much as foo above except that as soon as we have created it we appear to be throwing it away.

In practice what define is doing [1] is:

define foo (function (a b) {
  a + b
})

with foo now available to be used in some way.

Notice the extra set of parentheses around the elements of the anonymous function. Much like you might parenthesise a sub-expression in arithmetic, say, 5 * (1 + 2), the parentheses force the evaluation of the anonymous function to be a function value, hence the define statement is more like:

define foo {function-value}

foo, or, rather, the function value that foo is referencing, could be passed around as an argument or invoked:

foo 2 3

should return 5.

Closure Parameters

The formal parameters of a closure (named or anonymous) can take a confusing set of forms primarily because of support for a varargs parameter but also because of the two ways closures are created.

In general, if you specify n formal (“positional”) parameters then the caller must supply n arguments when the closure is invoked. Any more or less is an error.

If you specify a varargs parameters in addition to n formal parameters then the user must supply at least n arguments and any remaining arguments are bundled up into a list. If there were no extra arguments, the list is #n (the empty list) otherwise the list will be the extra arguments.

If we assume there is a body of some kind following these stub-expressions:

define

function

notes

define (foo)

function #n

no parameters at all

define (foo a b)

function (a b)

two positional parameters

define (foo a b & c)

function (a b & c)

two positional parameters and a varargs parameter c

define (foo & c)

function c

no positional parameters and only a varargs parameter c

Note that:

  • in the first example, function is given #n, the empty list, directly. define always uses pt formals as the list of parameters and the pt of a list of one element is #n.

  • in the final example, function is given a single symbol instead of a list of parameters to indicate there is only a varargs parameter.

By way of example, epitomising elegance (or legitimising laziness), the function list is defined as:

define (list & x) x

Here, list only takes a varargs parameter, that is all of its arguments are bundled up into a list by the evaluator. As list’s job is to return a list from its arguments and the evaluator has done all the heavy lifting then list’s body is simply to return the list it was given as its varargs parameter x.

Closure Environment

When a closure is created it is associated with the current frame of execution and the current module. Together these describe both the locally defined (through containing functions’ parameters) local variables as well as the set of free variables defined in this and any imported modules.

When a closure is run all it has from the calling environment are the (evaluated) arguments passed to it. When the VM starts executing the instructions of the closure it will be using the stored execution frame and module associated with the closure’s definition.

Special Forms

Special forms exist only in the evaluator and cannot be extended or altered.

They are also invoked differently. Rather than “evaluate” each argument and pass the evaluated values to the special form, the arguments are passed verbatim: numbers, strings, lists, etc..

The special form can invoke its associated behavioural code. By and large that behavioural code is about processing those arguments such that byte code can be generated and subsequently run.

For more details see special forms.

Templates

Templates allow users to “create code.” They are implemented much like special forms in that no arguments are evaluated but are passed verbatim. The result of a template should be something that can be immediately re-evaluated.

Attention

Using templates is fraught with complications in that they are run by the evaluator, in other words, not at the time user code is running, and their result is recursively re-evaluated giving their operation a meta quality to them. They are also evaluated in a different environment (memory space, if you like).

As if that isn’t enough, the entity the user sees when using a template is actually a function, called an expander, which hides the template functionality.

Function Predicates

function function? o

test if o is a procedure

Param o:

object to test

Return:

#t if o is a procedure, #f otherwise

A procedure can be either an Idio (closure) or C (primitive) defined function

function primitive? o

test if o is a primitive

Param o:

object to test

Return:

#t if o is a primitive, #f otherwise

function expander? o

is o an expander

Param o:

value to test

Return:

an entry from the expanders table if o is an expander or #f

Function Constructors

In addition to the special forms define and function/name and function there are some templates which augment the function creating process.

In particular we can add the concept of optional and keyword arguments.

template define* [args]

define a named function with optional and keyword arguments

define* works with function*/name as define works with function/name.

template function* formals body

function* ... is a simple wrapper to:

function*/name (gensym 'anon) ...
template function*/name name formals body

define a named function with optional and keyword arguments

The formals for function* are interpreted as the (SRFI-89) grammar extended-formals:

extended_formals     ::=  variable | ( extended_def_formals )
extended_def_formals ::=    positional_section named_section? rest_section
                          | named_section? positional_section rest_section
positional_section   ::=  required_positional* optional_positional*
required_positional  ::=  variable
optional_positional  ::=  "(" variable expression ")"
named_section        ::=  named+
named                ::=    required_named
                          | optional_named
required_named       ::=  "(" keyword variable ")"
optional_named       ::=  "(" keyword variable expression ")"
rest_section         ::=  "&" variable
                          | empty
Example:

Define a function f which takes one normal positional parameter, a, and an optional positional parameter, b, which defaults to #f:

define* (f a (b #f)) {
  list a b
}

f 1                  ; '(1 #f)
f 1 2                ; '(1 2)

A keyword does not need to be the same word as the variable it sets so here we define a function g which takes a normal positional parameter, a, and an optional keyword parameter, :mykey, which is accessed in the function as b and defaults to #f:

Example:

define* (g a (:mykey b #f)) {
  list a b
}

f 1                  ; '(1 #f)
f 1 :mykey 2         ; '(1 2)

Unlike regular function definitions, optional_positional and named parameters can default to expressions involving previous parameters.

Here, the optional positional parameter b defaults to a * 2 and the optional keyword parameter, :mykey, accessed in the function as c defaults to a * b:

Example:

define* (h a (b (a * 2)) (:mykey c (a * b))) {
  list a b c
}

h 2                  ; '(2 4 8)
h 2 3                ; '(2 3 6)
h 2 3 :mykey 4       ; '(2 3 4)

Last built at 2024-05-17T06:10:57Z+0000 from 62cca4c (dev) for Idio 0.3.b.6