Using Nasal functions: Difference between revisions

Jump to navigation Jump to search
m
<syntaxhighlight> and tabulations - no custom tags!
m (<syntaxhighlight> and tabulations - no custom tags!)
Line 1: Line 1:
{{Template:Nasal Navigation}}
{{Template:Nasal Navigation}}


'''What is a function?''' A "function" is a piece of code that can be easily used repeatedly (without repeating the same code over and over again using your editor's copy&paste function), this is achieved by associating a symbolic name with the piece of code, such as "print", "show" or "get", setprop, getprop for example.


== Functions ==
== Simple examples ==
 
=== What is a function ? ===
 
A "function" is a piece of code that can be easily used repeatedly (without repeating the same code over and over again using your editor's copy&paste function), this is achieved by associating a symbolic name with the piece of code, such as "print", "show" or "get", setprop, getprop for example.


In its most basic form, a function does not even take any arguments. Let's imagine a function named '''hello''', functions are typically called by appending parentheses to the function name:
In its most basic form, a function does not even take any arguments. Let's imagine a function named '''hello''', functions are typically called by appending parentheses to the function name:


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
hello()
hello()
</syntaxhighlight>
</syntaxhighlight>


Line 17: Line 14:


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
hello();
hello();
</syntaxhighlight>
</syntaxhighlight>


Line 23: Line 20:


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
hello( hello() );
hello( hello() );
</syntaxhighlight>
</syntaxhighlight>


Line 31: Line 28:


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
hello( "FlightGear" );
hello( "FlightGear" );
</syntaxhighlight>
</syntaxhighlight>


Line 40: Line 37:
<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
var hello = func(who) {
var hello = func(who) {
print("Hello ", who);
    print("Hello ", who);
}
}
</syntaxhighlight>
</syntaxhighlight>
Line 52: Line 49:


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
var log_message = func {}
var log_message = func {}
</syntaxhighlight>
</syntaxhighlight>


=== Function bodies ===
== Function bodies ==


To add a function body, you need to add code in between these curly braces.
To add a function body, you need to add code in between these curly braces.


=== Anonymous function arguments ===
== Anonymous function arguments ==


In Nasal, arguments are by default implicitly passed in the "arg" array, not unlike perl. To understand how this works, you should probably first read up on Nasal vectors, which is just a fancy word for a list of things, that can be individually addressed by appending an index number.
In Nasal, arguments are by default implicitly passed in the "arg" array, not unlike perl. To understand how this works, you should probably first read up on Nasal vectors, which is just a fancy word for a list of things, that can be individually addressed by appending an index number.


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
var log_message = func {
var log_message = func {
     print(arg[0]);
     print(arg[0]);
}
}
</syntaxhighlight>
</syntaxhighlight>


Line 72: Line 69:


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
var log_message = func() {
var log_message = func() {
     print(arg[0]);
     print(arg[0]);
}
}
</syntaxhighlight>
</syntaxhighlight>


Line 88: Line 85:
As a side note, when arg... is not explicitly declared (e.g. func or func(a)), caller(0)[0] does not show "arg" and yet does not result in an undefined symbol error when I try and return arg. Why is this?
As a side note, when arg... is not explicitly declared (e.g. func or func(a)), caller(0)[0] does not show "arg" and yet does not result in an undefined symbol error when I try and return arg. Why is this?


===Named function arguments===
==Named function arguments==
You can also pass named arguments to a function, thus saving the typing and performance costs of extracting them from the arg array:
You can also pass named arguments to a function, thus saving the typing and performance costs of extracting them from the arg array:


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
var log_message = func(msg) {
var log_message = func(msg) {
     print(msg);
     print(msg);
}
}
</syntaxhighlight>
</syntaxhighlight>


The list of function arguments is called a function's "signature".
The list of function arguments is called a function's "signature".


=== Default values for function arguments ===
== Default values for function arguments ==


Function arguments can have default values, as in C++. Note that the default value must be a scalar (number, string, function, nil) and not a mutable composite object (list, hash).
Function arguments can have default values, as in C++. Note that the default value must be a scalar (number, string, function, nil) and not a mutable composite object (list, hash).


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
var log_message = func(msg="error") {
var log_message = func(msg="error") {
     print(msg);
     print(msg);
}
}
</syntaxhighlight>
</syntaxhighlight>


If some arguments have default values and some do not, those with default values must come first in the argument list:
If some arguments have default values and some do not, those with default values must come first in the argument list:


#Incorrect:
<syntaxhighlight lang="php">
var log_message = func(msg="error", line, object="ground") {  
#Incorrect:
#some code  
var log_message = func(msg="error", line, object="ground") {
    #some code
}
}
</syntaxhighlight>


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
#Correct:
#Correct:
var log_message = func(msg="error", object="ground", line) {  
var log_message = func(msg="error", object="ground", line) {
#some code  
    #some code
}
}
</syntaxhighlight>
</syntaxhighlight>
Line 126: Line 125:


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
listify = func(elements...) { return elements; }
listify = func(elements...) { return elements; }
listify(1, 2, 3, 4); # returns a list: [1, 2, 3, 4]
listify(1, 2, 3, 4); # returns a list: [1, 2, 3, 4]
</syntaxhighlight>
</syntaxhighlight>


=== Returning from functions ===
== Returning from functions ==


In Nasal, functions return implicitly the values of the last expression (e.g. "nil" in empty function bodies), you can also add an explicit "return" statement, for example to leave a function early. In addition, it is possible to return values, too. Whenever a function's signature is empty, because it doesn't take any named arguments, the parentheses are optional, as is the terminating semicolon after a func block:
In Nasal, functions return implicitly the values of the last expression (e.g. "nil" in empty function bodies), you can also add an explicit "return" statement, for example to leave a function early. In addition, it is possible to return values, too. Whenever a function's signature is empty, because it doesn't take any named arguments, the parentheses are optional, as is the terminating semicolon after a func block:
Line 136: Line 135:
So, semantically, these are all equivalent:
So, semantically, these are all equivalent:


var log_message = func() {};
<syntaxhighlight lang="php">
var log_message = func() {};


var log_message = func() {}
var log_message = func() {}


var log_message = func {return;}
var log_message = func {return;}


var log_message = func {nil;} # the semicolon here is also optional
var log_message = func {nil;} # the semicolon here is also optional


var log_message = func {};  
var log_message = func {};  


var log_message = func return;
var log_message = func return;


var log_message = func nil;
var log_message = func nil;


var log_message = func() nil;
var log_message = func() nil;


var log_message = func;
var log_message = func;
</syntaxhighlight>


===Named arguments in function calls===
==Named arguments in function calls==
Nasal supports named function arguments in function calls, too.
Nasal supports named function arguments in function calls, too.


Line 162: Line 163:


Examples:
Examples:
#if we have functions defined:
<syntaxhighlight lang="php">
var log_message = func (msg="") { #some code to log variable msg }
#if we have functions defined:
var lookat =  func (heading=0, pitch=0, roll=0, x=nil, y=nil, z=nil, time=hil, fov=20) { #some code using those variables }
var log_message = func (msg="") { #some code to log variable msg }
var lookat =  func (heading=0, pitch=0, roll=0, x=nil, y=nil, z=nil, time=hil, fov=20) { #some code using those variables }


#we can use them them the usual way with comma separated list of arguments:
#we can use them them the usual way with comma separated list of arguments:
log_message("Hello World!");
log_message("Hello World!");
lookat (180, 20, 0, XO, YO, ZO, now, 55);
lookat (180, 20, 0, XO, YO, ZO, now, 55);


#or we can use the hash literal arguments instead:
#or we can use the hash literal arguments instead:
log_message(msg:"Hello World!");
log_message(msg:"Hello World!");
lookat(heading:180, pitch:20, roll:0, x:X0, y:Y0, z:Z0,time:now, fov:55);
lookat(heading:180, pitch:20, roll:0, x:X0, y:Y0, z:Z0,time:now, fov:55);
</syntaxhighlight>


Both methods for calling the functions above are equivalent, but note the the second method is more readable, less prone to error, and self-documenting in the code for the function call.
Both methods for calling the functions above are equivalent, but note the the second method is more readable, less prone to error, and self-documenting in the code for the function call.
Line 178: Line 181:
As another example, consider:
As another example, consider:


var setPosition = func (latitude_deg, longitude_deg, altitude_ft) {
<syntaxhighlight lang="php">
  # do something here  
var setPosition = func (latitude_deg, longitude_deg, altitude_ft) {
}
# do something here  
# the actual function call:
}
setPosition( latitude_deg:34.00, longitude_deg:7.00, alt_ft:10000);
# the actual function call:
setPosition( latitude_deg:34.00, longitude_deg:7.00, alt_ft:10000);
</syntaxhighlight>


In other words, such function calls become much more self-explanatory because everybody can see immediately what a value is doing.
In other words, such function calls become much more self-explanatory because everybody can see immediately what a value is doing.
This is a good practice, as you may eventually have to take a longer break, away from your code - and then even you yourself will come to appreciate such small things that make code more intuitive to work with.
This is a good practice, as you may eventually have to take a longer break, away from your code - and then even you yourself will come to appreciate such small things that make code more intuitive to work with.


Declared arguments are checked and defaulted as would be expected: it's an error if you fail to pass a value for an undefaulted argument, missing default arguments get assigned as usual, and any rest parameter (e.g. "func(a,b=2,rest...){}") will be assigned with an empty vector.
Declared arguments are checked and defaulted as would be expected: it's an error if you fail to pass a value for an undefaulted argument, missing default arguments get assigned as usual, and any rest parameter (e.g. <tt>func(a,b=2,rest...){}</tt>) will be assigned with an empty vector.


=== Specifying parameters at call-time ===
== Specifying parameters at call-time ==


The previously discussed syntax also makes it possible to have functions without any named function arguments in their signature, while specifying arguments only during invocation:
The previously discussed syntax also makes it possible to have functions without any named function arguments in their signature, while specifying arguments only during invocation:
Line 195: Line 200:
<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
var print_pos = func {
var print_pos = func {
print('lat:',lat, ' lon:', lon, '\n');
    print('lat:',lat, ' lon:', lon, '\n');
}
}


Line 207: Line 212:
Also note that the standard arg vector, including its ellipsis form, is no longer available when using a hash to initialize the parameters of a function call.
Also note that the standard arg vector, including its ellipsis form, is no longer available when using a hash to initialize the parameters of a function call.


===Nested functions, implicit return ===
==Nested functions, implicit return ==
Also, Nasal functions can be easily nested, for example:
Also, Nasal functions can be easily nested, for example:


  var calculate = func(param1,param2,operator) {
<syntaxhighlight lang="php">
var calculate = func(param1,param2,operator) {
   var add = func(p1,p2) {p1+p2;}
   var add = func(p1,p2) {p1+p2;}
   var sub = func(p1,p2) {p1-p2;}
   var sub = func(p1,p2) {p1-p2;}
Line 219: Line 225:
   if (operator=="*") return mul(param1,param2);
   if (operator=="*") return mul(param1,param2);
   if (operator=="/") return div(param1,param2);
   if (operator=="/") return div(param1,param2);
  }
}
</syntaxhighlight>


Note that the add,sub,mul and div functions in this example do not make use of an explicit return statement, instead the result of each expression is implicitly returned to the caller.
Note that the add,sub,mul and div functions in this example do not make use of an explicit return statement, instead the result of each expression is implicitly returned to the caller.
Line 225: Line 232:
Nasal functions that just consist of such simple expressions can also be further simplified to read:
Nasal functions that just consist of such simple expressions can also be further simplified to read:


  var add = func(val1,val2) val1+val2;
<syntaxhighlight lang="php">
var add = func(val1,val2) val1+val2;
</syntaxhighlight>


=== Function overloading ===
== Function overloading ==


Note that Nasal functions can generally not be [http://en.wikipedia.org/wiki/Function_overloading overloaded], and that operator overloading in particular is also not supported.
Note that Nasal functions can generally not be [http://en.wikipedia.org/wiki/Function_overloading overloaded], and that operator overloading in particular is also not supported.
Line 237: Line 246:
To provide an example, here's a simple function to multiply two numbers, no matter if they are provided as scalars, as a vector or as x/y members of a hash:
To provide an example, here's a simple function to multiply two numbers, no matter if they are provided as scalars, as a vector or as x/y members of a hash:


var multiply2 = func (params) {
<syntaxhighlight lang="php">
  if (typeof(params)=="scalar") return params*arg[0];
var multiply2 = func (params) {
  if (typeof(params)=="vector") return params[0]*params[1];
    if (typeof(params)=="scalar") return params*arg[0];
  if (typeof(params)=="hash")  return params.x*params.y;
    if (typeof(params)=="vector") return params[0]*params[1];
  die("cannot do what you want me to do");
    if (typeof(params)=="hash")  return params.x*params.y;
}
    die("cannot do what you want me to do");
}
</syntaxhighlight>


So, now you have a very simple form of an "overloaded" function that supports different argument types and numbers:
So, now you have a very simple form of an "overloaded" function that supports different argument types and numbers:


multiply2(  2,6); # multiply two scalars
<syntaxhighlight lang="php">
multiply2( [5,7] ); # multiply two scalars stored in a vector
multiply2(  2,6); # multiply two scalars
multiply2( {x:8, y:9} ); # multiply two scalars stored in a hash
multiply2( [5,7] ); # multiply two scalars stored in a vector
multiply2( {x:8, y:9} ); # multiply two scalars stored in a hash
</syntaxhighlight>


You could obviously extend this easily to support an arbitrary number of arguments by just using a for loop here.
You could obviously extend this easily to support an arbitrary number of arguments by just using a for loop here.
Line 256: Line 269:
Another common technique to "overload" a function is to get a handle to the original function and then re-implement the function at your script's scope, so that calls to settimer/setlistener will automatically invoke your own version of the function, this is explained at [[Developing and debugging Nasal code#Managing timers and listeners]].
Another common technique to "overload" a function is to get a handle to the original function and then re-implement the function at your script's scope, so that calls to settimer/setlistener will automatically invoke your own version of the function, this is explained at [[Developing and debugging Nasal code#Managing timers and listeners]].


=== Storing Functions ===
== Storing Functions ==
Functions can also be easily stored in vector and/or hashes:
Functions can also be easily stored in vector and/or hashes:


Line 262: Line 275:
var hash = {};
var hash = {};
hash.hello = func {
hash.hello = func {
print ("Hello !");
    print ("Hello !");
}
}


var vector = [];
var vector = [];
append(vector, func {
append(vector, func {
print("Hello !");
    print("Hello !");
});
});


Line 280: Line 293:
# the vector call:
# the vector call:
vector[0]();
vector[0]();
</syntaxhighlight>
</syntaxhighlight>


===Function closures===
==Function closures==
Each time a func {...} expression is evaluated, a new function object is created that is bound both to the namespace that it was evaluated in and to the currently executing function, which in turn is bound to an outer namespace and possibly another function/closure. This creates a list of namespaces, or closures, that represents the different possible scopes for a symbol name: a symbol can either be resolved in the local namespace of the function (created while executing that function) or in one of the successive closures. Since a closure is just a reference to a hash (the namespace), an outer closure may still exist even if it isn't accessible from a global namespace or the local namespace of an executing function (think stack frames in C), since it is kept accessible by the reference from the function object. For example:
Each time a <tt>func {...}</tt> expression is evaluated, a new function object is created that is bound both to the namespace that it was evaluated in and to the currently executing function, which in turn is bound to an outer namespace and possibly another function/closure. This creates a list of namespaces, or closures, that represents the different possible scopes for a symbol name: a symbol can either be resolved in the local namespace of the function (created while executing that function) or in one of the successive closures. Since a closure is just a reference to a hash (the namespace), an outer closure may still exist even if it isn't accessible from a global namespace or the local namespace of an executing function (think stack frames in C), since it is kept accessible by the reference from the function object. For example:


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
Line 300: Line 312:
As evidenced by the example, a function can remember the local namespace of the function that created it (e.g. the local namespace of <tt>generator</tt>) and can also remember the same closures that the generator had (which in this example would be both the namespace that contains the outer variable as well as the global namespace, whatever that might be).
As evidenced by the example, a function can remember the local namespace of the function that created it (e.g. the local namespace of <tt>generator</tt>) and can also remember the same closures that the generator had (which in this example would be both the namespace that contains the outer variable as well as the global namespace, whatever that might be).


====Misuse of closures====
===Misuse of closures===


What's wrong this this code?
What's wrong this this code?
Line 338: Line 350:
(for the record, bind returns the function it just rebound, even though it works via mutation, so essentially it is a convenience return of the first argument.)
(for the record, bind returns the function it just rebound, even though it works via mutation, so essentially it is a convenience return of the first argument.)


===Functional programming, higher order functions, generators (advanced concept) ===
==Functional programming, higher order functions, generators (advanced concept) ==
As previously mentioned, arguments to a Nasal function can also be functions themselves (Nasal being a functional programming language), this means that Nasal functions are higher order functions so that you can easily pass and return functions to and from Nasal functions. This can for example be used to dynamically create new functions (such functions are commonly called 'generators'):
As previously mentioned, arguments to a Nasal function can also be functions themselves (Nasal being a functional programming language), this means that Nasal functions are higher order functions so that you can easily pass and return functions to and from Nasal functions. This can for example be used to dynamically create new functions (such functions are commonly called 'generators'):


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
  # a function that returns a new custom function
# a function that returns a new custom function
  var i18n_hello = func(hello) {
var i18n_hello = func(hello) {
  return func(name) { # returns an anonymous/unnamed function
    return func(name) { # returns an anonymous/unnamed function
    print(hello,name);
        print(hello,name);
  }
    }
  }
}
 
  # create three new functions
# create three new functions
  var english_hello = i18n_hello("Good Day ");
var english_hello = i18n_hello("Good Day ");
  var spanish_hello = i18n_hello("Buenos Dias ");
var spanish_hello = i18n_hello("Buenos Dias ");
  var italian_hello = i18n_hello("Buon giorno ");
var italian_hello = i18n_hello("Buon giorno ");
 
  # actually call these functions
# actually call these functions
  english_hello("FlightGear");
english_hello("FlightGear");
  spanish_hello("FlightGear");
spanish_hello("FlightGear");
  italian_hello("FlightGear");
italian_hello("FlightGear");
</syntaxhighlight>
</syntaxhighlight>


=== Using helper functions ===
== Using helper functions ==


It is possible to simplify complex function calls by introducing small helper functions, for example consider:
It is possible to simplify complex function calls by introducing small helper functions, for example consider:


var l = thermalLift.new(ev.lat, ev.lon, ev.radius, ev.height, ev.cn, ev.sh, ev.max_lift, ev.f_lift_radius);
<syntaxhighlight lang="php">
 
var l = thermalLift.new(ev.lat, ev.lon, ev.radius, ev.height, ev.cn, ev.sh, ev.max_lift, ev.f_lift_radius);
</syntaxhighlight>


So, you could just as well create a small helper function named"thermalLift.new_from_ev(ev)":
So, you could just as well create a small helper function named"thermalLift.new_from_ev(ev)":


  thermalLift.new_from_ev = func (ev) {
<syntaxhighlight lang="php">
  thermalLift.new(ev.lat, ev.lon, ev.radius, ev.height, ev.cn, ev.sh, ev.max_lift, ev.f_lift_radius);
thermalLift.new_from_ev = func (ev) {
}
    thermalLift.new(ev.lat, ev.lon, ev.radius, ev.height, ev.cn, ev.sh, ev.max_lift, ev.f_lift_radius);
}


var l=thermalLift.new_from_ev(ev);
var l=thermalLift.new_from_ev(ev);
</syntaxhighlight>


Note that the expression to invoke your code would then also become less complicated and much more comprehensible.
Note that the expression to invoke your code would then also become less complicated and much more comprehensible.
Line 380: Line 395:
When you have expressions of nested method calls, such as:
When you have expressions of nested method calls, such as:


    t.getNode("latitude-deg").setValue(f.getNode("latitude-deg").getValue());
<syntaxhighlight lang="php">
    t.getNode("longitude-deg").setValue(f.getNode("longitude-deg").getValue());
t.getNode("latitude-deg").setValue(f.getNode("latitude-deg").getValue());
t.getNode("longitude-deg").setValue(f.getNode("longitude-deg").getValue());
</syntaxhighlight>


You could just as easily introduce a small helper function to wrap the code, that would be less typing for you, less code to read (and understand) for others and generally it would help localize functionality (and possible errors):
You could just as easily introduce a small helper function to wrap the code, that would be less typing for you, less code to read (and understand) for others and generally it would help localize functionality (and possible errors):


    var copyNode = func(t,f,path) t.getNode(path).setValue(f.getNode(path).getValue());
<syntaxhighlight lang="php">
var copyNode = func(t,f,path) t.getNode(path).setValue(f.getNode(path).getValue());
</syntaxhighlight>


So you would simply take the complex expression and generalize it by adding variables that you pass in from a function object, then you could simply call your new function like this:
So you would simply take the complex expression and generalize it by adding variables that you pass in from a function object, then you could simply call your new function like this:


    copyNode(t,f,"latitude-deg");
<syntaxhighlight lang="php">
    copyNode(t,f,"longitude-deg");
copyNode(t,f,"latitude-deg");
copyNode(t,f,"longitude-deg");
</syntaxhighlight>


or:
or:


    foreach(var p; ["latitude-deg", "longitude-deg","generated-flag"])
<syntaxhighlight lang="php">
      copyNode(t,f,p);
foreach(var p; ["latitude-deg", "longitude-deg","generated-flag"])
    copyNode(t,f,p);
</syntaxhighlight>


or as a complete function accepting a vector of properties:
or as a complete function accepting a vector of properties:


    var copyNode = func(target,source,properties) {  
<syntaxhighlight lang="php">
    if (typeof(properties)!="vector") properties=[properties];
var copyNode = func(target,source,properties) {
    if (typeof(target)!="hash") target=props.globals.getNode(target);
    if (typeof(properties)!="vector") properties=[properties];
    if (typeof(source)!="hash") target=props.globals.getNode(source)
    if (typeof(target)!="hash") target=props.globals.getNode(target);
    foreach(var path; properties)
    if (typeof(source)!="hash") target=props.globals.getNode(source)
      target.getNode(path).setValue( source.getNode(path).getValue() );
    foreach(var path; properties)
    }
        target.getNode(path).setValue( source.getNode(path).getValue() );
}


    copyNode("/temp/test", "/position", ["latitude-deg", "longitude-deg", "altitude-ft"]);
copyNode("/temp/test", "/position", ["latitude-deg", "longitude-deg", "altitude-ft"]);
</syntaxhighlight>


Whenever you have very similar lines of code that seem fairly repetitive, it is a good idea to consider introducing small helper functions. You can use plenty of small helper functions and then just "chain" them together, rather than using complex nested expressions that make your head spin.
Whenever you have very similar lines of code that seem fairly repetitive, it is a good idea to consider introducing small helper functions. You can use plenty of small helper functions and then just "chain" them together, rather than using complex nested expressions that make your head spin.


=== Another way to call functions ===
== Another way to call functions ==
... using '''call()'''. Sometimes, you may need to more finegrained control over functions that you want to call, such as specifying an object for a method call, or for exception handling purposes. This is accomplished using Nasal's '''call()''' API:
... using '''call()'''. Sometimes, you may need to more finegrained control over functions that you want to call, such as specifying an object for a method call, or for exception handling purposes. This is accomplished using Nasal's '''call()''' API:


395

edits

Navigation menu