Using Nasal functions: Difference between revisions

Jump to navigation Jump to search
m
lang="php" -> lang="nasal" (I assume Gijs will run his bot later - it looks pretty good to me!)
m (→‎Named arguments: s/hil/nil/g)
m (lang="php" -> lang="nasal" (I assume Gijs will run his bot later - it looks pretty good to me!))
Line 13: Line 13:
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, empty parenthese for function calls without any arguments.
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, empty parenthese for function calls without any arguments.


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
hello()
hello()
</syntaxhighlight>
</syntaxhighlight>
Line 19: Line 19:
As a single statement, you'd want to terminate the instruction using a semicolon:
As a single statement, you'd want to terminate the instruction using a semicolon:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
hello();
hello();
</syntaxhighlight>
</syntaxhighlight>
Line 25: Line 25:
However, you do not always need to terminate a function call, especially not embedded calls, imagine this:
However, you do not always need to terminate a function call, especially not embedded calls, imagine this:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
hello( hello() );
hello( hello() );
</syntaxhighlight>
</syntaxhighlight>
Line 36: Line 36:
The truth is, functions don't always have empty parentheses - the parentheses are there to pass function arguments (parameters) to the function:
The truth is, functions don't always have empty parentheses - the parentheses are there to pass function arguments (parameters) to the function:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
hello( "FlightGear" );
hello( "FlightGear" );
</syntaxhighlight>
</syntaxhighlight>
Line 45: Line 45:
Imagine having a piece of code that multiplies two vectors of numbers with each other. Now, if you wanted to use this code in different places, you would have to repeat (copy/paste) the same code over and over again. This is where we instead assign a symbolic name to the code we want to re-use and wrap it in curly braces, the implementation of the hello function (its function body) may look like this:
Imagine having a piece of code that multiplies two vectors of numbers with each other. Now, if you wanted to use this code in different places, you would have to repeat (copy/paste) the same code over and over again. This is where we instead assign a symbolic name to the code we want to re-use and wrap it in curly braces, the implementation of the hello function (its function body) may look like this:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var hello = func( name ) {
var hello = func( name ) {
     print("Hello ", name);
     print("Hello ", name);
Line 59: Line 59:
As previously shown, Nasal functions are implemented using the func keyword, The following snippet of code defines a new function named "log_message" with an empty function body (the curly braces).
As previously shown, Nasal functions are implemented using the func keyword, The following snippet of code defines a new function named "log_message" with an empty function body (the curly braces).


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var log_message = func {}
var log_message = func {}
</syntaxhighlight>
</syntaxhighlight>
Line 73: Line 73:
So, semantically, these are all equivalent:
So, semantically, these are all equivalent:


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


Line 101: Line 101:
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 square brackets:
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 square brackets:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var log_message = func {
var log_message = func {
     print(arg[0]);
     print(arg[0]);
Line 109: Line 109:
Note that this is basically equivalent to:
Note that this is basically equivalent to:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var log_message = func() {
var log_message = func() {
     print(arg[0]);
     print(arg[0]);
Line 125: Line 125:
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="nasal">
var log_message = func(msg) {
var log_message = func(msg) {
     print(msg);
     print(msg);
Line 137: Line 137:
Named function arguments can have default values, as in C++ or Python. Note that the default value must be a constant (number, string, function, or nil) and not a mutable composite object (list, hash).
Named function arguments can have default values, as in C++ or Python. Note that the default value must be a constant (number, string, function, or nil) and not a mutable composite object (list, hash).


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var log_message = func(msg="error") {
var log_message = func(msg="error") {
     print(msg);
     print(msg);
Line 145: Line 145:
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:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
#Incorrect:
#Incorrect:
var log_message = func(msg="error", line, object="ground") {
var log_message = func(msg="error", line, object="ground") {
Line 152: Line 152:
</syntaxhighlight>
</syntaxhighlight>


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
#Correct:
#Correct:
var log_message = func(msg="error", object="ground", line) {
var log_message = func(msg="error", object="ground", line) {
Line 161: Line 161:
Any extra arguments after the named list are placed in the "arg" vector as above. You can rename this to something other than "arg" by specifying a final argument name with an ellipsis:
Any extra arguments after the named list are placed in the "arg" vector as above. You can rename this to something other than "arg" by specifying a final argument name with an ellipsis:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
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]
Line 174: Line 174:
The most commonly used method for specifying arguments is a simple comma-separated list of expressions, which get mapped to the named arguments, overflowing into the "arg" symbol if needed (or the argument declared with ... after it). Here's a series of examples illustrating this:
The most commonly used method for specifying arguments is a simple comma-separated list of expressions, which get mapped to the named arguments, overflowing into the "arg" symbol if needed (or the argument declared with ... after it). Here's a series of examples illustrating this:
<!-- enclose="div" for wrapping -->
<!-- enclose="div" for wrapping -->
<syntaxhighlight lang="php" enclose="div">
<syntaxhighlight lang="nasal" enclose="div">
# Repeated from above: a simple function with a defaulted argument
# Repeated from above: a simple function with a defaulted argument
var log_message = func(msg="error") {
var log_message = func(msg="error") {
Line 196: Line 196:


Examples:
Examples:
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
#if we have functions defined:
#if we have functions defined:
var log_message = func (msg="") {
var log_message = func (msg="") {
Line 218: Line 218:
As another example, consider:
As another example, consider:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var setPosition = func (latitude_deg, longitude_deg, altitude_ft) {
var setPosition = func (latitude_deg, longitude_deg, altitude_ft) {
     # do something here
     # do something here
Line 235: Line 235:
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:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var print_pos = func {
var print_pos = func {
     print('lat:',lat, ' lon:', lon, '\n');
     print('lat:',lat, ' lon:', lon, '\n');
Line 252: Line 252:
Also, Nasal functions can be easily nested, for example:
Also, Nasal functions can be easily nested, for example:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var calculate = func(param1,param2,operator) {
var calculate = func(param1,param2,operator) {
   var add = func(p1,p2) {p1+p2;}
   var add = func(p1,p2) {p1+p2;}
Line 269: Line 269:
Nasal functions that just consist of such simple expressions can also be further simplified by ommitting the braces:
Nasal functions that just consist of such simple expressions can also be further simplified by ommitting the braces:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var add = func(p1,p2) p1+p2;
var add = func(p1,p2) p1+p2;
</syntaxhighlight>
</syntaxhighlight>
Line 283: Line 283:
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:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var multiply2 = func (params) {
var multiply2 = func (params) {
     if (typeof(params)=="scalar") return params*arg[0];
     if (typeof(params)=="scalar") return params*arg[0];
Line 294: Line 294:
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:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
multiply2(  2,6); # multiply two scalars
multiply2(  2,6); # multiply two scalars
multiply2( [5,7] ); # multiply two scalars stored in a vector
multiply2( [5,7] ); # multiply two scalars stored in a vector
Line 309: Line 309:
Functions can also be easily stored in vector and/or hashes:
Functions can also be easily stored in vector and/or hashes:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var hash = {};
var hash = {};
hash.hello = func {
hash.hello = func {
Line 335: Line 335:
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:
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="nasal">
var outer = {};
var outer = {};
var generator = func(mid) {
var generator = func(mid) {
Line 352: Line 352:


What's wrong this this code?
What's wrong this this code?
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var result = [];
var result = [];
for (var i=0; i<10; i+=1)
for (var i=0; i<10; i+=1)
Line 364: Line 364:
How to fix this? The obvious fix is to keep a reference to a copy as the closure and there are two ways to make that happen. The first way is using a generator function, and the second way uses bind(). The first way is really simple:
How to fix this? The obvious fix is to keep a reference to a copy as the closure and there are two ways to make that happen. The first way is using a generator function, and the second way uses bind(). The first way is really simple:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
for (var i=0; i<10; i+=1)
for (var i=0; i<10; i+=1)
     (func {
     (func {
Line 373: Line 373:
</syntaxhighlight>
</syntaxhighlight>
Or one could write it like this (which I prefer for minute reasons related to the garbage collector/GC):
Or one could write it like this (which I prefer for minute reasons related to the garbage collector/GC):
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
# arguments are local variables, so this form can do the same thing:
# arguments are local variables, so this form can do the same thing:
var generator = func(i) return func print(i);
var generator = func(i) return func print(i);
Line 381: Line 381:


The other way (which produces a nearly identical result) uses the builtin bind() function. When a func {...} expression is evaluated, there are two Nasal variables that are involved: one is a naCode and the other is a naFunc. The naCode is stored as a constant for the function and it stores information about the arguments and the body; the naFunc is simply a wrapper that makes the naCode executable and gives it a closure. Each time the VM evaluates the func expression, this creates another naFunc from the naCode base with a unique closure. If you call <tt>bind(fn, caller(0)[0], caller(0)[1])</tt> (where fn is the function you just created), you essentially do nothing – as the naFunc is, by default, bound to the caller like so. However, changing the second argument to a different hash, say a unique one for each iteration, will change the outer namespace of the function while keeping the rest of the closure intact (this last part is important for keeping the global namespace as a closure, so that the print function can be found). For example, using an empty hash with our first example snippet and bind() will produce an undefined symbol error, since we effectively removed the closure that contained the i variable. But if we create a hash such as <tt>{i:i}</tt>, then we will create a faux-namespace with the i variable – but this variable won't be affected by the for loop, and thus we will have accomplished our goal. This is the completed code:
The other way (which produces a nearly identical result) uses the builtin bind() function. When a func {...} expression is evaluated, there are two Nasal variables that are involved: one is a naCode and the other is a naFunc. The naCode is stored as a constant for the function and it stores information about the arguments and the body; the naFunc is simply a wrapper that makes the naCode executable and gives it a closure. Each time the VM evaluates the func expression, this creates another naFunc from the naCode base with a unique closure. If you call <tt>bind(fn, caller(0)[0], caller(0)[1])</tt> (where fn is the function you just created), you essentially do nothing – as the naFunc is, by default, bound to the caller like so. However, changing the second argument to a different hash, say a unique one for each iteration, will change the outer namespace of the function while keeping the rest of the closure intact (this last part is important for keeping the global namespace as a closure, so that the print function can be found). For example, using an empty hash with our first example snippet and bind() will produce an undefined symbol error, since we effectively removed the closure that contained the i variable. But if we create a hash such as <tt>{i:i}</tt>, then we will create a faux-namespace with the i variable – but this variable won't be affected by the for loop, and thus we will have accomplished our goal. This is the completed code:
<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
for (var i=0; i<10; i+=1)
for (var i=0; i<10; i+=1)
     append(result, bind(func print(i), {i:i}, caller(0)[1]);
     append(result, bind(func print(i), {i:i}, caller(0)[1]);
Line 390: Line 390:
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="nasal">
# 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) {
Line 413: Line 413:
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:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var l = 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(ev.lat, ev.lon, ev.radius, ev.height, ev.cn, ev.sh, ev.max_lift, ev.f_lift_radius);
</syntaxhighlight>
</syntaxhighlight>
Line 419: Line 419:
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)":


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
thermalLift.new_from_ev = func (ev) {
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);
     thermalLift.new(ev.lat, ev.lon, ev.radius, ev.height, ev.cn, ev.sh, ev.max_lift, ev.f_lift_radius);
Line 432: Line 432:
When you have expressions of nested method calls, such as:
When you have expressions of nested method calls, such as:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
t.getNode("latitude-deg").setValue(f.getNode("latitude-deg").getValue());
t.getNode("latitude-deg").setValue(f.getNode("latitude-deg").getValue());
t.getNode("longitude-deg").setValue(f.getNode("longitude-deg").getValue());
t.getNode("longitude-deg").setValue(f.getNode("longitude-deg").getValue());
Line 439: Line 439:
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):


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var copyNode = func(t,f,path) t.getNode(path).setValue(f.getNode(path).getValue());
var copyNode = func(t,f,path) t.getNode(path).setValue(f.getNode(path).getValue());
</syntaxhighlight>
</syntaxhighlight>
Line 445: Line 445:
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:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
copyNode(t,f,"latitude-deg");
copyNode(t,f,"latitude-deg");
copyNode(t,f,"longitude-deg");
copyNode(t,f,"longitude-deg");
Line 452: Line 452:
or:
or:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
foreach(var p; ["latitude-deg", "longitude-deg","generated-flag"])
foreach(var p; ["latitude-deg", "longitude-deg","generated-flag"])
     copyNode(t,f,p);
     copyNode(t,f,p);
Line 459: Line 459:
or as a complete function accepting a vector of properties:
or as a complete function accepting a vector of properties:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
var copyNode = func(target,source,properties) {
var copyNode = func(target,source,properties) {
     if (typeof(properties)!="vector") properties=[properties];
     if (typeof(properties)!="vector") properties=[properties];
Line 476: Line 476:
... 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:


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
call(function, vector_of_arguments, object, namespace, error_vector);
call(function, vector_of_arguments, object, namespace, error_vector);
</syntaxhighlight>
</syntaxhighlight>
395

edits

Navigation menu