1.20Functional programming support

ETA functions and functional constructs.

Falcon provides some special functional programming constructs that are known to the VM to have special significance. The vast majority of them starts a "functional evaluation" chain on their parameters before their value is evaluated. A functional evaluation is a recursive evaluation (reduction) of list structures into atoms. At the moment, the only list structure that can be evaluated this way is the array. Evaluating a parameter in functional context means that the given parameter will be recursively scanned for callable arrays or symbols that can be reduced to atoms. A callable array is reduced by calling the function and substituting it with its return value. When all the contents of the list are reduced, the higher level is evaluated.

Consider this example:


   function func0( p0, p1 ): ...
   function func1( p0 ): ...

   list = [func0, [func1, param1], param2]

Calling list as a callable array, func0 will be called with the array [func1, param1] as the first parameter, and param2 as the second parameter. On the other hand, evaluating the above list in a functional context, first func1 will be called with param1, then func0 will be called with the return value of the previous evaluation as the first parameter, and with param2 as the second parameter.

The functions in this section are considered "special constructs" as the VM knows them and treats them specially. Their definition overrides the definition of a functional evaluation, so that when the VM finds a special construct in its evaluation process, it ceases using the default evaluation algorithm and passes evaluation control to the construct.

For example, the iff construct selects one of its branches to be evaluated only if the first parameter evaluates to true:


   list = [iff, someValueIsTrue, [func0, [func1, param1]], [func1, param2] ]

If this list had to be evaluated in a functional context, then before iff had a chance to decide what to do, the two arrays [func0, ...] and [func1,...] would have been evaluated. As iff is a special construct, the VM doesn't evaluate its parameters and lets iff perform its operations as it prefer. In the case o iff, it first evaluates the first parameter, then evaluates in functional context the second on the third parameter, leaving unevaluated the other one.

Not all constructs evaluates everything it is passed to them in a functional context. Some of them are meant exactly to treat even a callable array (or anything else that should be reduced) as-is, stopping the evaluation process as the VM meets them. The description of each construct explains its working principles, and whether if its parameters are evaluated or not.

Please, notice that "callable" doesn't necessarily mean "evaluable". To evaluate in functional context a callable symbol without parameter, it must be transformed into a single-element array. For example:


   function func0(): ...

   result = [iff, shouldEval, [func0], func0]

This places in result the value returned by func0 if shouldEval is true, while it returns exactly the function object func0 as-is if shouldEval is false.

A more formal definition of the funcional programming support in Falcon is provided in the Survival Guide.

Functions

all

Returns true if all the items in a given collection evaluate to true.

all( sequence )
sequence A sequence of arbitrary items.
Returntrue if all the items are true, false otherwise

Items in sequence are evaluated in functional context for truth value. This means that, if they are sigmas, they get sigma-reduced and their return value is evaluated, otheriwise they are evaluated directly.

Truth value is determined using the standard Falcon truth check (nil is false, numerics are true if not zero, strings and collections are true if not empty, object and classes are always true).

The check is short circuited. This means that the processing of parameters is interrupted as an element is evaluated into false.

If the collection is empty, this function returns false.

allp

Returns true if all the parameters evaluate to true.

allp( ... )
... An arbitrary list of items.
Returntrue if all the items are true, false otherwise

This function works like all, but the collection may be specified directly in the parameters rather being given in a separate array. This make easier to write allp in callable arrays. For example, one may write


      [allp, 1, k, n ...]

while using all one should write


      [all, [1, k, n ...]]

Parameters are evaluated in functional context. This means that, if they are sigmas, they get sigma-reduced and their return value is evaluated, otheriwise they are evaluated directly.

Truth value is determined using the standard Falcon truth check (nil is false, numerics are true if not zero, strings and collections are true if not empty, object and classes are always true).

If called without parameters, this function returns false.

any

Returns true if any of the items in a given collection evaluate to true.

any( sequence )
sequence A sequence of arbitrary items.
Returntrue at least one item in the collection is true, false otherwise.

Items in sequence are evaluated in functional context for truth value. This means that, if they are sigmas, they get sigma-reduced and their return value is evaluated, otheriwise they are evaluated directly.

Truth value is determined using the standard Falcon truth check (nil is false, numerics are true if not zero, strings and collections are true if not empty, object and classes are always true).

The check is short circuited. This means that elements are evaluated until an element considered to be true (or sigma-reduced to a true value) is found.

If the collection is empty, this function returns false.

anyp

Returns true if any one of the parameters evaluate to true.

anyp( ... )
... A list of arbitrary items.
Returntrue at least one parameter is true, false otherwise.

This function works like any, but the sequence may be specified directly in the parameters rather being given in a separate array. This make easier to write anyp in callable arrays. For example, one may write


      [anyp, 1, k, n ...]

while using any one should write


      [any, [1, k, n ...]]

Parameters are evaluated in functional context. This means that, if they are sigmas, they get sigma-reduced and their return value is evaluated, otheriwise they are evaluated directly.

Truth value is determined using the standard Falcon truth check (nil is false, numerics are true if not zero, strings and collections are true if not empty, object and classes are always true).

If called without parameters, this function returns false.

brigade

Process a list of functions passing the same parameters to them.

brigade( fl, ... )
fl The sequence of callable items to be called.
... Arbitrary parameters used by the brigade functions.
ReturnThe return value of the last function in fl.

This function process a sequence of functions passing them the same set of parameters. The idea is that of a "brigate" of functions operating all on the same parameters so that it is possible to put common code into separate functions.

Items in the list are not functionally evaluated; they are simply called, passing to them the same parameters that the brigade group receives. Brigate is an ETA funcion, and this means that ongoing functional evaluation is interrupted as soon as a brigade is encountered.


   function mean( array )
      value = 0
      for elem in array: value += elem
      return value / len( array )
   end

   function dbl( array )
      for i in [0:len(array)]: array[i] *= 2
   end

   doubleMean = .[ brigade .[
      dbl
      mean
   ]]

   > "Mean: ", mean( [1,2,3] )
   > "Double mean: ", doubleMean( [1,2,3] )

The above example brigades a "prefix" function to double the values in an array that must be processed by the main function.

Using out of band return values, the functions in the sequence can also control the parameters that the following functions will receive, terminate immediately the evaluation or restart it, forming a sort of iterative functional loop. An oob 0 causes the sequence to be interrutped (and return oob(0) itself), an out of band 1 will cause the first element of the sequence to be called again, and an out of band array will permanently change the parameters as seen by the functions.

The following brigate performs a first step using the given parameters, and another one using modified ones:


   looper = .[brigade .[
      { val, text => printl( text, ": ", val ) } // do things
      { val, text => oob( [val+1, "Changed"] ) }  // change params
      { val, text => val >= 10 ? oob(0) : oob(1)}  // loop control
   ]]

   looper( 1, "Original" )

cascade

Concatenate a set of callable items so to form a single execution unit.

cascade( callList, [...] )
callList Sequence of callable items.
... Optional parameters to be passed to the first callable item.
ReturnThe return value of the last callable item.

This function executes a set of callable items passing the parameters it receives beyond the first one to the first item in the list; from there on, the return value of the previous call is fed as the sole parameter of the next call. In other words,


      cascade( [F1, F2, ..., FN], p1, p2, ..., pn )

is equivalent to


      FN( ... F2( F1( p1, p2, ..., pn ) ) ... )

A function may declare itself "uninterested" to insert its value in the cascade by returning an out-of-band item. In that case, the return value is ignored and the same parameter it received is passed on to the next calls and eventually returned.

Notice that the call list is not evaluated in functional context; it is just a list of callable items. To evaluate the list, or part of it, in functional context, use the eval() function.

A simple example usage is the following:


      function square( a )
         return a * a
      end

      function sqrt( a )
         return a ** 0.5
      end

      cascade_abs = [cascade, [square, sqrt] ]
      > cascade_abs( 2 )      // 2
      > cascade_abs( -4 )     // 4

Thanks to the possibility to prevent insertion of the return value in the function call sequence, it is possible to program "interceptors" that will catch the progress of the sequence without interfering:


      function showprog( v )
         > "Result currently ", v
        return oob(nil)
      end

      // define sqrt and square as before...
      cascade_abs = [cascade, [square, showprog, sqrt, showprog] ]
      > "First process: ", cascade_abs( 2 )
      > "Second process: ", cascade_abs( -4 )

If the first function of the list declines processing by returning an oob item, the initial parameters are all passed to the second function, and so on till the last call.

For example:


      function whichparams( a, b )
         > "Called with ", a, " and ", b
         return oob(nil)
      end

      csq = [cascade, [ whichparams, {a,b=> a*b} ]
      > csq( 3, 4 )

Here, the first function in the list intercepts the parameters but, as it doesn't accepts them, they are both passed to the second in the list.

See also: oob.

choice

Selects one of two alternatives depending on the evaluation of the first parameter.

choice( selector, whenTrue, [whenFalse],[...] )
selector The item to be evaluated.
whenTrue The item to return if selector evaluates to true.
whenFalse The item to be returned if selector evaluates to false
... Optional parameters to be passed to the first callable item.
ReturnThe return value of the last callable item.

The selector parameter is evaluated in functional context. If it's a true atom or if it's a callable array which returns a true value, the ifTrue parameter is returned as-is, else the ifFalse parameter is returned. If the ifFalse parameter is not given and the selector evaluates to false, nil is returned.

The choice function is equivalent to iff where each branch is passed through the lit function:


      choice( selector, a, b ) == iff( selector, [lit, a], [lit, b] )

In case a literal value is needed, choice is more efficient than using iff and applying lit on the parameters.

dolist

Repeats an operation on a list of parameters.

dolist( processor, sequence, [...] )
processor A callable item that will receive data coming from the sequence.
sequence A list of items that will be fed in the processor one at a time.
... Optional parameters to be passed to the first callable item.
ReturnThe return value of the last callable item.

Every item in sequence is passed as parameter to the processor, which must be a callable item. Items are also functionally evaluated, one by one, but the parameter sequence is not functionally evaluated as a whole; to do that, use the explicit evaluation:


      dolist( processor, eval(array) )

This method is equivalent to xmap, but it has the advantage that it doesn't create an array of evaluated results. So, when it is not necessary to transform a sequence in another through a mapping function, but just to run repeatedly over a collection, this function is to be preferred.

eval

Evaluates a sequence in functional context.

eval( sequence )
sequence A sequence to be evaluated.
ReturnThe sigma-reduction (evaluation) result.

The parameter is evaluated in functional context; this means that if the parameter is a sequence starting with a callable item, that item gets called with the rest of the sequence passed as parameters, and the result it returns is considered the "evaluation result". This is performed recursively, inner-to-outer, on every element of the sequence before the call to the first element is actually performed.

The description of the functional evaluation algorithm is included in the heading of this section.

filter

Filters sequence using a filter function.

filter( ffunc, sequence )
ffunc A callable item used to filter the array.
sequence A sequence of arbitrary items.
ReturnThe filtered sequence.

ffunc is called iteratively for every item in the collection, which is passed as a parameter to it. If the call returns true, the item is added to the returned array; if it returns false, the item is not added.

Items in the collection are treated literally (not evaluated).

firstOf

Returns the first non-false of its parameters.

firstOf( ... )
... Any number of arbitrary parameters.
ReturnThe first non-false item.

This function scans the paraters one at a time. Sigma evaluation is stopped, or in other words, every parameters is considered as-is, as if lit was used on each of them. The function returns the first parameter being non-false in a standard Falcon truth check. Nonzero numeric values, non empty strings, arrays and dictionaries and any object is considered true.

If none of the parameters is true, of is none of the parameter is given, the function returns nil (which is considered false).

floop

Repeats indefinitely a list of operations.

floop( sequence )
sequence A sequence of callable items that gets called one after another.

Every item in sequence gets executed, one after another. When the last element is executed, the first one is called again, looping indefinitely. Any function in the sequence may interrupt the loop by returning an out-of-band 0; if a function returns an out of band 1, all the remaining items in the list are ignored and the loop starts again from the first item.

Items in the array are not functionally evaluated.

iff

Performs a functional if; if the first parameter evaluates to true, the second parameter is evaluated and then returned, else the third one is evaluated and returned.

iff( cfr, whenTrue, [whenFalse] )
cfr A condition or a callable item.
whenTrue Value to be called and/or returned in case cfr evaluates to true.
whenFalse Value to be called and/or returned in case cfr evaluates to false.
ReturnThe evaluation result of one of the two branches (or nil).

Basically, this function is meant to return the second parameter or the third (or nil if not given), depending on the value of the first parameter; however, every item is evaluated in a functional context. This means that cfr may be a callable item, in which case its return value will be evaluated for truthfulness, and also the other parameters may. For example:


      > iff( 0, "was true", "was false" )           // will print "was false"
      iff( [{a=>a*2}, 1] , [printl, "ok!"] )       // will print "ok!" and return nil

In the last example, we are not interested in the return value (printl returns nil), but in executing that item only in case the first item is true. The first item is a callable item too, so iff will first execute the given block, finding a result of 2 (true), and then will decide which element to pick, and eventually execute. Notice that:


      iff( 1 , printl( "ok!" ), printl( "no" ) )

This would have forced Falcon to execute the two printl calls before entering the iff function; still, iff would have returned printl return values (which is nil in both cases).

lbind

Creates a dynamic late binding symbol.

lbind( name, [value] )
name A string representing a late binding name.
value A future binding value.
ReturnA newly created late binding name.

This function create a late binding item which can be used in functional sequences as if the parameter was written in the source code prefixed with the amper '&' operator.

The following lines are equivalent:


      bound = lbind( "count" )
      ctx = &count

The return value of this function, both used directly or pre-cached, can be seamlessly merged with the & operator in functional sequences.

For example, it is possible to write the following loop:


      eval( .[
         .[ times 10 &count .[
            .[eval .[ printl 'Counting...' .[lbind 'count'] ] ]
            ]
         ]] )

It is also possible cache the value and use it afterwards:


      x = lbind( 'count' )
      eval( .[
         .[ times 10 &count .[
            .[ printl 'Counting...' x]
            ]
         ]] )

The value parameter initializes a future (forward) binding. Future bindings are bindings with a potential value, which is applied during function calls and symbol resolution to pre-existing symbolic entities. In practice, they allow calling fucntions with named parameters.

When mixing forward bindings and normal parameters, forward bindings are as placed directly at the position of the parameter they refer to, and they doesn't count during parameter expansion of non-named parameters. Also, they always overwrite the positional parameters, as they are considered after all the positional parameters have been placed on their spots.


      function test( par1, par2, par3 )
         >> "Par1: ", par1
         >> ", Par2: ", par2
         >  ", Par3: ", par3
      end

      x = lbind( "par2", "Hello" )

      test( x )                       // nil->par1, "Hello"->par2, nil->par3
      test( x, "Yo!" )                // "Yo!"->par1, "Hello"->par2, nil->par3
      test( "Yo!", x )                // as above
      test( "Yo!", "Yo! again", x )   // "Hello" overwrites "Yo again"
      test( x, "Yo!", "Yo! again", "end" )   // "Yo!"->par1, "Hello"->par2, "end"->par3

Note: lbind is not an ETA function.

let

Assigns a value to another in a functional context.

let( dest, source )
dest Destination value (passed by reference).
source Source value.
ReturnThe assigned (source) value.

This function assigns a literal value given in source into dest, provided dest is a late binding or is passed by referece.

This function is an ETA and prevents evaluation of its first parameter. In other words, the first parameter is treadted as if passed through lit.

lit

Return its parameter as-is

lit( item )
item A condition or a callable item.
ReturnThe parameter unevaluated.

This function is meant to interrupt functional evaluation of lists. It has the same meaning of the single quote literal ' operator of the LISP language.

For example, the following code will return either a callable instance of printl, which prints a "prompt" before the parameter, or a callable instance of inspect:


      iff( a > 0, [lit, [printl, "val: "] ], inspect)( param )

as inspect is a callable token, but not an evaluable one, it is already returned literally; however, [printl, "val:"] would be considered an evaluable item. To take its literal value and prevent evaluation in functional context, the lit construct must be used.

map

Creates a new vector of items transforming each item in the original array through the mapping function.

map( mfunc, sequence )
mfunc A function or sigma used to map the array.
sequence A sequence of arbitrary items.
ReturnThe parameter unevaluated.

mfunc is called iteratively for every item in the collection; its return value is added to the mapped array. In this way it is possible to apply an uniform transformation to all the item in a collection.

If mfunc returns an out of band nil item, map skips the given position in the target array, actually acting also as a filter function.

For example:


      function mapper( item )
         if item < 0: return oob(nil)  // discard negative items
         return item ** 0.5            // perform square root
      end

   inspect( map( mapper, [ 100, 4, -12, 9 ]) )    // returns [10, 2, 3]

See also: oob.

max

Picks the maximum value among its parameters.

max( ... )
... The items to be checked.
ReturnThe greatest item in the sequence.

This function performs a lexicographic majority check on each element passed as a parameter, returning the greater of them.

A standard VM comparation is performed, so the standard ordering rules apply. This also means that objects overloading the BOM.compare method may provide specialized ordering rules.

If more than one item is found equal and greater than all the others, the first one is returned.

If the function is called without parameters, it returns nil.

min

Picks the minimal value among its parameters.

min( ... )
... The items to be checked.
ReturnThe smallest item in the sequence.

This function performs a lexicographic minority check on each element passed as a parameter, returning the smallest of them.

A standard VM comparation is performed, so the standard ordering rules apply. This also means that objects overloading the BOM.compare method may provide specialized ordering rules.

If more than one item is found equal and lesser than all the others, the first one is returned.

If the function is called without parameters, it returns nil.

reduce

Uses the values in a given sequence and iteratively calls a reductor function to extract a single result.

reduce( reductor, sequence, [initial_value] )
reductor A function or Sigma to reduce the array.
sequence A sequence of arbitrary items.
initial_value Optional startup value for the reduction.
ReturnThe reduced result.

The reductor is a function receiving two values as parameters. The first value is the previous value returned by the reductor, while the second one is an item iteratively taken from the origin array. If a startup value is given, the first time the reductor is called that value is provided as its first parameter, otherwise the first two items from the array are used in the first call. If the collection is empty, the initial_value is returned instead, and if is not given, nil is returned. If a startup value is not given and the collection contains only one element, that element is returned.

Some examples:


   > reduce( {a,b=> a+b}, [1,2,3,4])       // sums 1 + 2 + 3 + 4 = 10
   > reduce( {a,b=> a+b}, [1,2,3,4], -1 )  // sums -1 + 1 + 2 + 3 + 4 = 9
   > reduce( {a,b=> a+b}, [1] )            // never calls lambda, returns 1
   > reduce( {a,b=> a+b}, [], 0 )          // never calls lambda, returns 0
   > reduce( {a,b=> a+b}, [] )             // never calls lambda, returns Nil

Items in the collection are treated literally (not evaluated).

times

Repeats a sequence a determined number of times.

times( count, sequence )
count Count of times to be repeated or non-open range.
sequence A function or a Sigma sequence.
ReturnLast index processed.

This function is very similar to a functional for/in loop. It repeats a sequence of callable items in the sequence parameter a determined number of times. If the sequence parameter is a sequence, parametric evaluation is performed and the &1 late binding is filled with the value of the index; if it's a function, then it is called with the counter value added as the last parameter.

If the evaluated parameter is a sequence, full deep sigma evaluation is performed at each loop.

The loop index count will be given values from 0 to the required index-1 if count is numeric, or it will act as the for/in loop if count is a range.

For example:



      function printParam( var )
         > "Parameter is... ", var
      end

      // the followings are equivalent
      times( 10, [printParam, &1] )
      times( 10, printParam )

The following example prints a list of pair numbers between 2 and 10:


      times( [2:11:2],     // range 2 to 10+1, with step 2
         .[ printl "Index is now..." &1 ]
         )

Exactly like floop, the flow of calls in times can be altered by the functions in sequence returning an out-of-band 0 or 1. If any function in the sequence returns an out-of-band 0, times terminates and return immediately (performing an operation similar to "break"). If a function returns an out-of-band 1, the rest of the items in sequence are ignored, and the loop is restarted with the index updated; this is equivalent to a functional "continue". For example:


   times( 10,
             // skip numbers less than 5
      .[ .[(function(x); if x < 5: return oob(1); end)  &1]
         .[printl &1]   // print the others
       ]
    )

The times function return the last generated value for the index. A natural termination of times can be detected thanks to the fact that the index is equal to the upper bound of the range, while an anticipated termination causes times to return a different index. For example, if count is 10, the generated index (possibly received by the items in sequence) will range from 0 to 9 included, and if the function terminates correctly, it will return 10. If a function in sequence returns an out-of-band 0, causing a premature termination of the loop, the value returned by times will be the loop index at which the out-of-band 0 was returned.

Note: Ranges [m:n] where m > n (down-ranges) terminate at n included; in that case, a succesful completion of times return one-past n.

valof

Calls callable items or returns non callable ones.

valof( item )
item The item to be checked.
ReturnThe item if it is not callable, or the call return value.

The name function is a short for extended value. It is meant to determine if the passed item is a non-callable value or if it should be called to determine a value. Performing this check at script level time consuming and often clumsy, and this function is easily used in functional sequences.

xmap

Creates a new vector of items transforming each item in the original array through the mapping function, applying also filtering on undesired items.

xmap( mfunc, sequence )
mfunc A function or sigma used to map the array.
sequence A sequence to be mapped.
ReturnThe mapped sequence.

mfunc is called iteratively for every item in the collection; its return value is added to the mapped array. Moreover, each item in the collection is functionally evaluated before being passed to mfunc.

The filter function may return an out of band nil item to signal that the current item should not be added to the final collection.

For example:



      mapper = { item => item < 0 ? oob(nil) : item ** 0.5 }
      add = { a, b => a+b }         // a block that will be evaluated

      inspect( xmap( mapper, [ [add, 99, 1], 4, -12, 9 ]) )    // returns [10, 2, 3]

See also: oob, dolist.

See also: oob, dolist.

Made with http://www.falconpl.org