Falcon Wiki - Survival:Functions
- The functions
- Anonymous and nested functions
- Callable arrays
- Accessing the calling context
- Non positional parameters
- Variable parameter passing
- Variable aliases and pass by reference
The functions
Functions are pieces of code that may be reused again and again by calling them with different values called parameters. More formally, functions are relational operators that relate a set of zero or more values (called parameters) that can be taken from a finite or infinite set of possible values (called a dominion) with exactly one item that can be taken from a finite or infinite set of possible values (called a co-dominion).
Meet our first function:
function do_something( parameter ) printl( "Hey, this is a function saying: ", parameter ) end do_something( "Hello world" ) do_something( 15 ) /* again, ad libitum */
Functions are declared by the function keyword, followed by a symbol and two parenthesis which can contain a list of zero or more parameters. In this case, the function doesn't return any value; actually this is an illusion, because a function that does not return explicitly a value will be considered as returning the special value nil.
Functions can even be declared after being used:
do_something( "Hello world" ) do_something( 15 ) function do_something( parameter ) printl( "Hey, this is a function saying: ", parameter ) end
All the code that is not in a function is considered to be "the main program"; function lines are kept separated from normal code, and so it is possible to intermix code and functions like this:
do_something( "Hello world" ) function do_something( parameter ) printl( "Hey, this is a function saying: ", parameter ) end do_something( 15 )
Anyhow, it is very important to have a visual reference to where the "real program" begins, if it ever does, so in real scripts you should really take care to separate functions from the other parts of the script, and show clearly where the function section or main section begins:
/* This is my script */ do_something( "Hello world" ) do_something( 15 ) /************************************* Main section over, starting with functions. **************************************/ function do_something( parameter ) printl( "Hey, this is a function saying: ", parameter ) end
Or if you prefer a bottom-top approach:
/* This is my script */ function do_something( parameter ) printl( "Hey, this is a function saying: ", parameter ) end /************************************* Main program begins here. **************************************/ do_something( "Hello world" ) do_something( 15 )
As many other statements, functions executing just one statement may be abbreviated with the colon indicator (":").
Functions are not just useful to do something, but also to return some values:
function square( x ) y = x * x return y end
or more briefly:
function square( x ): return x * x
Return will immediately exit the function body and return the control to the calling code. For example:
function some_branch( x ) if x > 10000 return "This number is too big" end /* do something */ return "Number processed" end
The first return prevents the rest of the function to be performed. It is also possible to return from inside loops.
A function may be called with any number of parameters. If less than the declared parameters are passed to the function, the missing ones will be filled with nil.
Recursiveness
Functions can call other functions, and surprisingly, they can also call themselves. The technique of a function calling itself is called {{http://en.wikipedia.org/wiki/Recursion_%28computer_science%29|recursion]]. For example, you may calculate the sum of the first N numbers with a loop, but this is more fun:
function sum_of_first( x ) if x > 1 return x + sum_of_first( x - 1 ) end return x end
Explaining how to use the recursion (and when to prefer it to a loop) is beyond the scope of this document but if you’re interested here is a start point.
Local and global variable names
Whenever you declare a parameter or assign variable in a function for the first time, that name becomes "local". This means that even if you declared a variable with the same name in the main program, the local version of the variable will be used instead. This prevents accidentally overwriting a variable that may be useful elsewhere; look at this example.
sqr = 1.41 function square( x ) sqr = x * x return sqr end number = square( 8 ) * sqr
If the sqr name inside the function were not protected, the variable in the main program would have been overwritten.
Global variables can be accessed by functions, but normally they cannot be overridden. For example:
sqr = 1.41 function square( x ) printl( "sqr was: ", sqr ) sqr = x * x return sqr end number = square( 8 ) * sqr
will print 1.41 in the square function; however, when the sqr variable is rewritten in the very next line, this change is visible only to the function that caused the change.
Anyhow, sometimes it's useful to modify to an external variable from a function without having that variable being passed as a parameter. In this case, the function can "import" the global variable with the keyword global .
function square_in_z( x ) global z z = x * x end z = 0 square_in_z( 8 ) printl( z ) // 64
Static local variables and initializers
Sometimes it's useful to have a function that remembers how its variables were configured when it was last called. Using global imported names could work, but is inelegant and dangerous as the names may be used to do something beyond the control of the function. Falcon provides a powerful construct that is called a "static initializer". The statements inside the static initializer are executed only once, the first time the function is ever called, and the variables that are assigned in it are then "recorded", and they stay the same up to the next call.
This example shows a loop that iteratively calls a function with a static initializer. Each time it’s called, the function returns a different value:
function say_something() static data = [ "have", "a", "nice", "day" ] current = 0 end if current == len( data ) return end element = data[current] current += 1 return element end thing = say_something() while thing != nil print( thing, " " ) thing = say_something() end printl()
The first time say_something is called, the static statements are executed, and two static variables are initialized. The static block can contain any statement, and be of any length, just any local variables that are initialized in the static block will retain their value across calls.
We have also seen the nil special value in action; when the function returns nothing, signaling that it has nothing left to say, the thing variable is filled with the nil special value, and the loop is terminated.
Anonymous and nested functions
Normally, functions are top-level statements. This means that the "parent" of a function must be a Falcon module. However, it is possible to create anonymous functions in every part of the code, through the function keyword, or through a more synthetic code-block declaration (see the next section).
Anonymous functions are so declared:
//... at any level... function ( [p1, ..., pn] ) [static block] [statements] end
The only difference with regular functions is that the function keyword is not followed by a name.
Anonymous functions are seen by the parser as expressions; as such, they can be assigned to variables, called, returned, used as parameters to call other functions directly after the end keyword is reached. Using a parenthesis around it may make the code more readable. In the following example, we return a function reversing the parameter in one case, and a function shuffling it in the other.
function ret_a_func( reverse ) if reverse: return ( function( param ) return param[-1:0] end ) return function( param ) a = [] for n in [0:param.len()]: a += param[n] return "".merge( randomGrab(a, a.len() ) ) end end // cache a reverse function rev = ret_a_func( true ) > rev( "hello" ) // or directly call the shuffler > ret_a_func( false )( "hello" )
Anonymous functions can be nested, as in the following example:
function test( a ) if a == 0 res = function ( b, c ) // nested anonymous function! l = function ( b ) return b * 2 end result = b * l(c) + 1 return result end else res = function ( b, c ); return b * c -1; end end return res end // instantiates the first anonymous function func0 = test( 0 ) // instantiates the second anonymous func1 = test( 1 ) printl( "First function result:", func0( 2, 2 ) ) printl( "Second function result:", func1( 2, 2 ) )
Anonymous functions respect the closure semantic. Variables declared in the surrounding functions (or parameters of the parent function) can be used by the anonymous functions for later usage.
For example, in the following code the operand parameter of the makeMultiplier function is stored as a part of the parametric expression in the anonymous function it returns.
function makeMultiplier( operand ) return function( value ) return value * operand end end m2 = makeMultiplier( 2 ) // ... by 2 > m2( 100 ) // will be 200 m4 = makeMultiplier( 4 ) // ... by 4 > m4( 100 ) // will be 400
Anonymous functions are useful constructs to automatize processes. Instead of coding a workflow regulated by complex branches, it is possible to select, use and/or return an anonymous function, reducing the size of the final code and improving readability, maintainability and efficiency of the program.
As with any other callable item, anonymous functions can also be generated by a factory function and shared across different modules.
To exploit this feature to its maximum, it is important to learn to think in terms of providing the higher levels (the main script) not just with data to process, but also with code to perform tasks.
Codeblocks
Codeblocks are mainly a syntactic sugar for anonymous functions, especially for functions just needing to perform an expression and return its value.
The formal declaration of codeblocks is the following:
{ [p1, p2..., pn] => expression } // or { [p1, p2..., pn] => statement ... }
where p1 ... pn is an optional list of parameters.
Codeblocks are seen as expressions at syntactic level, so they can be assigned, returned, sent to other functions as parameters, or generically treated as any expression.
Actual parameter values can be fed through the function call operator (open and close round parenthesis). For example, to print the sum of a number a rather complex way may be:
printl( {a, b => a + b}(2,2) )
Notice that codeblocks respect the functional closure semantic, so:
function makeMultiplier( o ): return { v => v * o } multBy2 = makeMultiplier( 2 ) > multBy2( 100 ) // will be 200
Codeblocks containing more than one single expression are totally equivalent to nameless functions; as for nameless functions, it is necessary to return a value from within them to make it visible in the caller. For example:
posiSum = { a, b => val = a + b if val < 0: return -val return val } > posiSum( -5, 1 ) // 4 > posiSum( 5, 1 ) // 6
Non-closure anonymous functions
The keyword innerfunc works exactly as the function keyword when declaring an anonymous function, with the major difference that it doesn't respect closure semantic. Exactly as for the anonymous functions and for the codeblocks, the innerfunc keyword is syntactically interpreted as an expression, so it can be assigned, called, returned, passed as a parameter and so on. The formal definition is as follows:
innerfunc ( [param1, param2, ..., paramN] ) [static block] [statements] end
The following is a working example:
square = innerfunc ( a ) return a * a end printl( "Square of 10: ", square( 10 ) )
The fact that innerfunc doesn't provide closure semantic may be used as a guard against errors in naming variables declared in the parent. For example, the following code will raise an undeclared variable error, while it would silently cause a disaster using the function keyword.
function do_things( pa1 ) // ... do things with pa1 square = innerfunc( p1 ) return pa1 * p1 // we wanted to write p1 * p1 end return square( 10 ) end > do_things( "hello" ) // supposed to return 100
Callable arrays
When the first element of an array is a function, the array becomes "callable". Applying the function call operator to it, the function used as first element is called. Other elements in the array are passed to the function as parameters; other parameters may be passed to the function normally, through the call operator.
For example, the following code always prints "hello world".
printl( "Hello world" ) [printl]( "Hello world" ) [printl, "Hello"]( " world" ) [printl, "Hello", " ", "world"]()
It is then possible to create pre-cached parameters functions. For example, to prepend every call to printl with a prompt, the following code may be used:
p_print = [printl, "prompt> "] p_print( "Hello world!" )
An interesting usage of arrays as functions with pre-cached parameters is that to store an item in the array by reference. Item references will be shown in a future chapter, but consider the following code:
i = 0 icall = .[printl $i ": "] for i in [0:10]: icall( "Looping..." )
Notice that in this code we have used the dot-square array declarator to avoid using commas between tokens.
The "$i" code extracts a reference out from the "i" variable; the called function will be then presented with the current value of the desired variable, and not with the value that it had when the callable array was created.
Callable arrays are considered callable items in every aspect. For example, they can be used everywhere a function is needed as a parameter (I.e. the arrayFilter() function), or in place of methods for objects.
Accessing the calling context
At times, it is useful to know:
- Who is the caller of a function.
- In which function we're currently working.
The keyword fself assumes the value of the currently executed function. It is syntactically equivalent to use the name of the function in which the fself keyword is used. For example:
function sumFirst_1( n ) return n <= 1 ? 1 : n + sumFirst_1( n-1 ) end // this is equivalent function sumFirst_2( n ) return n <= 1 ? 1 : n + fself( n-1 ) end
The fself keyword comes handy when the name of the current function is not known, as for code blocks and nameless functions.
> "Sum of first 30 numbers: ", { n => n <= 1 ? 1 : n + fself( n-1 ) } (30)
The caller method can be applied to functions to discover who called them.
function recurse( val ) if val <= 0: return 1 > recurse.caller(), ":", val // or fself.caller() return recurse( val-1 ) + val end recurse( 5 )
We'll see that when the caller is a method, this comes quite useful as we can guess which object originated a call for us.
Non positional parameters
It is possible to address function parameters by names through a construct called future binding. In short, a parameter with a given name can be filled by adding a '|' (pipe) sign after a single-word symbol, and giving a value or an expression right after. See the following example:
function f( alpha, beta, gamma ) > @"alpha: $alpha" > @"beta : $beta" > @"gamma: $gamma" end f( gamma| "a" + "b" + "c", beta|"b-value" )
This sets nil in alpha, "b-value" in beta and "abc" in gamma. The interesting point about "future bindings" is that they are actually expression; so the above f call is equivalent to:
future_beta = beta|"b-value" future_gamma = lbind( "gamma", "a" + "b" + "c" ) f( future_gamma, future_beta )
The lbind function can create late and future bindings; it's less efficient than the pipe operator, but it's more flexible as it can create bindings with arbitrary names.
A future binding value gets expanded in any function call, including callable array calls. Calling a function with a future binding having a name not matching any parameter name raises an error, like in the following case:
f( non_existing|"value" ) // raises an error!
It is possible to mix positional and non-positional parameters in the same call. In that case, the positional parameters are applied first, then all the non positional parameters (no matter where they appear in call order) are applied to the matching parameters. This may lead to effectively overwrite a value given in a positional parameter, as in the following example:
f( beta| "new value for beta", "in alpha", "in beta" )
As the first positional parameter is stored in alpha, the second in beta, and then the beta parameter is overwritten by the named parameter, the result is the following:
alpha: in alpha beta : new value for beta gamma: Nil
Variable parameter passing
Falcon supports variable parameter function calling. The compiler never checks for the number of parameters passed to a called function to count up to those that the function declares. In other words, the following code is perfectly acceptable:
function varcall( param1, param2 ) printl( "First parameter: ", param1 ) printl( "Second parameter: ", param2 ) end varcall( "one" ) varcall( "one", "two", "three" )
In the first call, param1 assumes the value of one; param2 is set to nil by the VM. In the second call, the third parameter is simply ignored.
In general, all the unused parameters will be set to nil. However, the target function can know the number of parameters it has been actually called with and can retrieve their value respectively with the functions paramCount and parameter:
function varcall() for i = 1 to paramCount() print( i ) switch ( i ) case 1: print( "st" ) case 2: print( "nd" ) case 3: print( "rd" ) default : print( "th" ) end // note: parameter count is zero based. printl( " parameter: ", parameter( i - 1 ) ) end printl( "End of function.\n") end varcall( "one" ) varcall( "one", "two", "three" ) varcall( "one", "two", "three", "four", "five" )
So, the target function is able to configure itself given the parameters that have been given it. It's also possible to declare parameters in the function header, and then use the parameter and paramCount functions to access to other parameters:
function varcall( p1, p2 ) printl( "Required params: ", p1, " and ", p2 ) print( "Complete list: " ) for i = 0 to paramCount() - 1 step 1 print( parameter( i ) ) if i != paramCount() - 1: print( ", " ) end printl( "." ) printl( "End of function.\n") end varcall( "one" ) varcall( "one", "two", "three" )
This example shows two features: first, it's possible to access via parameter() those parameters that have been declared in the function header. Second, the minimal number of parameter returned by paramCount() is the number of declared parameters (that's why the first call ends up printing two items). The declared parameters are considered mandatory and they are filled and provided to the target function even if the caller does not provide them. The idea is that you should use them only for those parameters that the caller really should provide, leaving any optional parameter for parameter() to take.
Accessing variable parameters
Falcon provides three functions to access all the parameters that have been sent to the function at once:
- passvp(): returns the parameters that have been passed but not declared (i.e. the variable parameters after the declared ones) as an array, or passes them to another item.
- argv(): Returns a vector containing all the received parameters, in the order they are passed.
- argd(): Returns a dictionary containing the parameters associated with their formal parameter name. Extra parameters (undeclared but received) are ignored.
The passvp function can be used to inspect extra parameters or to pass them to another callable item. For example, the following function prints extra parameters passing them to the printl function.
function passme( a, b ) > "A: ", a > "B: ", b // other parameters? > "Passvp result: ", passvp().describe() passvp( [printl, "Other params: "] ) end passme( 1, 2, 3 )
The functions argd() and argv() can be used to have more informations about the received parameters. Notice that argv() is always equivalent to passvp() when called in a function without formal parameters declared.
function param_infos( first, second, third ) > argv().describe() > argd().describe() end param_infos( "alpha", "beta", "gamma", "delta" )
Variable aliases and pass by reference
Falcon provides a powerful method to handle variables indirectly. Instead of having the symbol of a variable immediately available, it is possible to create an alias to a variable. References can also be used to pass parameters to the functions, making the function able to alter their value. A variable alias is created with the $ alias operator:
var = "original value" var_alias = $var // aliasing var to var_alias var_alias = "new value" printl( var ) // now will print "new value" function change_param( param ) param = "changed" end var = "original" change_param( var ) // pass by value printl( var ) // still "original" change_param( $var ) // pass by alias printl( var ) // now is "changed"
References are "sticky": they remain bound to the referencing variable until another reference is assigned to them. To remove the sticky reference from a variable, it is possible to assign a $$ to it, meaning just "stop being a reference to something". After this operation is done, the referencing variable will be set to nil, and further assignments to it will be considered as normal assignments.
It is not possible to return values by reference. To avoid having "floating references" to objects that may be no longer valid, any return value pointing to a reference is turned into a copy of the referenced item. Note that this doesn't mean that deep objects as arrays, dictionaries and instances will be duplicated; only the item itself is duplicated.
Accessing references in parameters
The use of reference variables and by-reference parameter passing has been previously explained. The Core module provides two functions that allow a function to interact with parameters that have been passed by reference without knowing them in advance.
The function paramIsRef ( <id> ) returns 1 if the nth parameter (starting from zero) has been passed by reference. The function paramSet ( <id>, <value> ) is able to set the nth parameter, as if it was directly set by the script:
function varcall( param1 ) param1 = "some value" paramSet( 0,"other value" ) printl( param1 ) // will print "other value" end
If the nth parameter has been passed by reference, then paramSet() will also change the value of the called item:
value = "original" varcall( value ) printl( "In main code: ", value ) // still original varcall( $value ) printl( "In main code again: ", value ) // now changed