logo
The Falcon Programming Language
A fast, easy and powerful programming language
Location: Home page >> Falcon wiki
User ID:
Password:
 

Falcon Wiki - Survival:Functions


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 

Navigation
Go To Page...

Loading

Elapsed time: 0.023 secs. (VM time 0.019 secs.)