Local functions and local value captures/Local function examples
This page shows (in no particular order) usage examples of Local Functions (and in some cases, value capture).
Mutables
The Mutables library which comes with Analytica provides an example that may be educational. Of course, if you need a mutable, we recommend using the library, but here we show how mutables are implemented using Local functions and local value captures.
Analytica's design intentionally excludes global Side effects from a Definition. But in rare cases, sometimes while debugging, it can be useful to "log" some data in memory without creating a data-dependency. We call a global in-memory storage that can be set, changed and read without maintaining any dependencies a mutable. The Mutables library provides a convenient set of functions for using mutables, which themselves are implemented via local functions and value capture.
A mutable is created with
Function MutableNew() ::= Local val := null; Array(Struct_Mutable, [ Function get( ) : val Do get, Function set( x ) : (val := x) Do set ])
where
- Index
Struct_Mutable ::= [ 'get', 'set' ]
The local, val
, holds the value of the mutable, and is captured by the two local functions that are returned in a struct by MutableNew. The get()
function reads the current value saved in val
, and set(x)
sets its value. The storage for val
outlives the call to MutableNew -- it is only released once both local functions are released.
Once you have a variable defined as:
- Variable
MyMutable ::= MutableNew()
You can read the current value using:
Function get( ) := MyMutable[Struct_Mutable='get'] Do get()
The function MutableGet in the Mutables library encapsulates this for you.
A simple Newton-Raphson function
The Newton-Raphson algorithm finds a zero of a function, i.e., a value x
such that F(x)=0
. It starts with a guess, [math]\displaystyle{ x_i }[/math], then jumps to a new guess using the update rule:
- [math]\displaystyle{ x_{i+1} = x_i - {{F(x)}\over{F'(x)}} }[/math]
and repeats until convergence to within ε of zero.
Here we implement a simple generic function that runs Newton-Raphson on any one-parameter scalar function. We keep it overly simplistic to emphasize the concepts of using local functions. In practice it would not be very robust to functions that change quickly or to local extrema that do not reach zero.
As a usage example, suppose you have a User-Defined Function:
Function MyFunc( x ) ::= (x + 3)*(x-1)
You could find a zero using:
FindAZero( MyFunc, guess:5 )
→ 1FindAZero( MyFunc, guess:-5 )
→ -3
Or you can use a local function -- this expression (starting with the word Function) appears in the Variable definition:
- Variable A_solution ::=
Function G(x) : (x+3)*(x-1) Do FindAZero( G, guess:5 )
The implementation of FindAZero needs a derivative of an arbitrary function.
Function dFdy( F : Function(x) ; x ; delta : optional = 1e-8 ) ::= (F(x+delta) - F(x-delta)) / (2*delta)
The simple Newton Raphson is thus:
Function FindAZero( ( F : Function(x) atom ; guess : scalar ) ::= Local x:=guess; Local iter := 0; While abs(F(x))>1e-6 and iter<100 Do ( iter := iter + 1; x := x - F(x) / dFdx(F,x) ); if iter>=100 then null else x
We declared the parameter «F» to be atomic (as well as «guess») so that if you call it on an array of functions, it processes one-at-a-time (because each While loop may require a different number of iterations).
A more robust Newton-Raphson would include bounds on an interval to search and/or on how far it can jump in one step, to prevent it from unpredictable behavior in divergent cases.
Enable comment auto-refresher