The following sections include various examples of procedure definition directly from the command-line. Of course in practice one would normally create such code in files, which would be sourced.
Tcl has facilities for automatically defining undefined commands when an attempt is made to
execute them.
In particular, the array
auto_index
contains the commands to define indexed commands.
Users with small libraries of their own procedures may prefer to simply source the
relevant files as part of Tcl startup.
The startup files distributed with NAP automatically source any file called
my.tcl
in the home directory.
This file can contain
source
commands to define one's own procedures.
[]
facility.
sind
sind".
The procedure can be defined on one line as follows:
% proc sind degrees {nap "sin(1r180p * degrees)"}
Note that
"1r180p"
is the constant π/180.
Now let's test function
"sind":
% nap "x = 0 .. 180 ... 30"
::NAP::76-76
% nap "y = sind x"
::NAP::83-83
% [nap "transpose(x /// y)"]
0 0
30 0.5
60 0.8660254
90 1
120 0.8660254
150 0.5
180 1.224606e-16
lam
Now let's define a function (with two arguments x and y) defined by the above expression
"transpose(x /// y)".
This is the transpose of the laminated arguments, so let's call it
"lam".
% proc lam {
x
y
} {
nap "z = x /// y"
nap "transpose z"
}
There are two lines in the body of this procedure.
The result of the final line defines the result of the function.
Testing:
% [nap "lam(x,y)"]
0 0
30 0.5
60 0.8660254
90 1
120 0.8660254
150 0.5
180 1.224606e-16
get_bin
Now let's define a function
"get_bin"
for binary input using the
"nap_get"
command:
% proc get_bin {
filename
{datatype {'f32'}}
{swap 0}
} {
# convert all arguments to strings
set filename [[nap "filename"]]
set datatype [[nap "datatype"]]
set swap [[nap "swap"]]
set channel [open $filename]
nap "in = [nap_get [lindex {binary swap} $swap] $channel $datatype]"
close $channel
nap "in"; # Define result
}
Note that the arguments
"datatype"
and
"swap"
have default values.
Also note how all three arguments are converted from NAP expressions to Tcl strings.
Now let's test it.
The following uses the OOC
binary
method to write six
f64
values to the file
"double.dat".
Then this file is read using function
"get_bin".
% set file [open double.dat w]
filee1eb10
% [nap "{1.5 -3 0 2 4 5}"] binary $file
% close $file
% nap "x = get_bin('double.dat', 'f64')"
::NAP::27-27
% $x all
::NAP::27-27 f64 MissingValue: NaN References: 1
Dimension 0 Size: 6 Name: (NULL) Coordinate-variable: (NULL)
Value:
1.5 -3 0 2 4 5
fact
Now let's define a factorial function called
"fact".
Of course we cannot resist the temptation to use recursion:
% proc fact n {
if {[$n] > 1} {
nap "n * fact(n-1)"
} else {
nap "1"
}
}
This works fine for scalar arguments:
% [nap "fact 4"] 24 % [nap "fact 1"] 1 % [nap "fact 0"] 1But the following shows that it fails for a vector argument!
% [nap "fact {0 1 4 6}"]
1
factorial
One can define a proper elemental factorial function as follows:
% proc factorial n {
if {[[nap "max(reshape(n)) > 1"]]} {
nap "n > 1 ? n * factorial(n-1) : 1"
} else {
nap "1"
}
}
% [nap "factorial {0 1 4 6}"]
1 1 24 720
Note the double brackets in the if command. The inner
brackets produce an OOC-name. The outer brackets execute
this OOC to produce the string
"0"
or
"1".
a(b)",
which is of course equivalent to
"a b".
NAP checks whether
"a"
is a Tcl variable.
If not, it is assumed to be a function.
In this case NAP first looks for a Tcl procedure called
"::NAP::a."
If this does not exist then NAP looks for a built-in NAP function called
"a".
If this does not exist then NAP looks for a Tcl procedure called
"a".
The following example shows that a procedure with the global name
"sin"
does not override the built-in function with that name,
whereas defining it within the NAP namespace
"::NAP::"
does override:
% proc sin x {nap "2*x"}
% [nap "sin 1"]
0.841471
% proc ::NAP::sin x {nap "2*x"}
% [nap "sin 1"]
2
It is possible to call some procedures as either functions or commands.
The following example defines and uses the same function
"sind"
defined above:
% proc sind degrees {nap "sin(1r180p * degrees)"}
% [nap "sind 30"]; # call as function
0.5
% nap "s = [sind 30]"; # call as command within NAP expression
::NAP::80-80
% $s
0.5
% [sind 30] all; # call as direct OOC
::NAP::86-86 f64 MissingValue: NaN References: 0
Value:
0.5
But there is a problem calling procedures as commands if the result is referenced by
a variable which is local to the procedure.
At the end of the procedure Tcl deletes such local variables.
This causes the referenced NAOs to be deleted.
For example we could redefine function
"sind"
as follows:
% proc sind degrees {
nap "result = sin(1r180p * degrees)"
nap "result"
}
% [nap "sind 30"]
0.5
% [sind 30]
invalid command name "::NAP::32-32"
Note that the call as a function still worked but not the call as a command. NAP operates in a special mode while executing a procedure called as a function. The deletion of NAOs referenced by local variables is delayed until after the result has been saved. This is one advantage of calling procedures as functions rather than commands.
write_expr
% proc write_expr {
expr
filename
} {
set channel [open $filename w]
puts $channel [[uplevel nap \"$expr\"] value]
close $channel
}
% nap "to = 5"
::NAP::52-52
% write_expr "1 .. to /// {0 7}" matrix.txt
% cat matrix.txt; # display contents of file 'matrix.txt'
1 2 3 4 5
0 7 0 7 0
get_binary
Next let's define a procedure called
"get_binary"
which is intended to be called as a command,
but does essentially the same thing as the above function
"get_bin".
This will help us to compare the two techniques in a situation where each has some
advatages and some disadvantages.
We assume the file
"double.dat"
still exists.
The following example defines and tests procedure
"get_binary":
% proc get_binary {
filename
{datatype f32}
{swap 0}
} {
set channel [open $filename]
nap "in = [nap_get [lindex {binary swap} $swap] $channel $datatype]"
close $channel
nap "+in"; # Define result as copy of 'in' to prevent premature deletion
}
% nap "x = [get_binary double.dat f64]"
::NAP::63-63
% $x all
::NAP::63-63 f64 MissingValue: NaN References: 1
Dimension 0 Size: 6 Name: (NULL) Coordinate-variable: (NULL)
Value:
1.5 -3 0 2 4 5
Note that
"get_binary"
is simpler to define and simpler to use than
"get_bin".
The main reason for this is the fact that all three arguments are used as strings
rather than NAOs.
One disadvantage of the command approach is the need to define the result as
"+in"
rather than simply
"in".