REBOL 3 Docs | Guide | Concepts | Functions | Datatypes | Errors |
TOC < Back Next > | Updated: 6-Feb-2009 Edit History |
You can define functions that work in the same way as native functions. These are called user-defined functions. User-defined functions are of the function! datatype.
You can make simple functions that require no arguments with the does function. This example defines a new function that prints the current time:
print-time: does [print now/time]
print-time
10:30
The does function returns a value, which is the new function. In the example, the print-time word is set to the function. However, this function value can be set to a word, passed to another function, returned as the result of a function, saved in a block, or immediately evaluated.
Functions that require arguments are made with the func function, which accepts two arguments:
func spec body
The first argument is a block that specifies the interface to the function. It includes a description of the function, its arguments, the types allowed for arguments, descriptions of the arguments, and other items. The second argument is a block of code that is evaluated whenever the function is evaluated.
Here is an example of a new function called sum:
sum: func [arg1 arg2] [arg1 + arg2]
The newly defined function accepts two arguments, as specified in the first block. The second block is the body of the function, which, when evaluated, adds the two arguments together. The new function is returned as a value from func and the sum word is set to it. Here it is in use:
print sum 123 321
444
The result of arg1 being added to arg2 is returned and printed.
The first block of a function definition is called its interface specification. This block includes a description of the function, its arguments, the datatypes allowed for arguments, descriptions of the arguments, and other items.
The interface specification is a dialect of REBOL (because it has different evaluation rules than normal code). The specification block has the format:
[ "function description" [optional-attributes] argument-1 [optional-type] "argument description" argument-2 [optional-type] "argument description" ... /refinement "refinement description" refinement-argument-1 [optional-type] "refinement argument description" ... ]
The fields of the specification block are:
Description | A short description of the function. This is a string that can be accessed by other functions such as help to output descriptions of functions. |
Attributes | A block that describes special properties of the function, such as its behavior on errors. It may be expanded in the future to include flags for optimizations. |
Argument | A variable that is used to access an argument from within the body of the function. |
Arg Type | A block that identifies the datatypes that are accepted by the function. If a datatype not identified in this block is passed to the function, an error will occur. |
Arg Description | A short description of the argument. Like the function description, this can be accessed by other functions such as help. |
Refinement | A refinement word that indicates special behavior is required of the function. |
Refinement Description | A short description of the refinement. |
Refinement Argument | A variable that is used by the refinement. |
Refinement Argument Type | A block that identifies the datatypes that are accepted by the refinement. |
Refinement Argument Description | A short description of the refinement argument. |
All of these fields are optional.
As an example, the argument block of the sum function (defined in a previous example) is expanded to restrict the type of arguments accepted. It also includes a description of the function and its expected arguments.
sum: func [ "Return the sum of two numbers." arg1 [number!] "first number" arg2 [number!] "second number" ][ arg1 + arg2 ]
Now, the datatype of the arguments is automatically checked, catching errors like:
print sum 1 "test" ** Script Error: sum expected arg2 argument of type: number. ** Where: print sum 1 "test"
To allow additional argument datatypes, more than one can be given:
sum: func [
"Return the sum of two numbers."
arg1 [number! tuple! money!] "first number"
arg2 [number! tuple! money!] "second number"
][
arg1 + arg2
]
print sum 1.2.3 3.2.1
4.4.4
print sum $1234 100
$1334.00
Now the sum function accepts a number, tuple, or monetary value as arguments. If within the function you need to distinguish what datatype was passed, you can use the datatype test functions:
if tuple? arg1 [print arg1] if money? arg2 [print arg2]
Because the sum function provided description strings, the help function now supplies useful information about it:
help sum USAGE: SUM arg1 arg2 DESCRIPTION: Return the sum of two numbers. SUM is a function value. ARGUMENTS: arg1 -- first number (Type: number tuple money) arg2 -- second number (Type: number tuple money)
As described earlier, the interpreter evaluates the arguments of functions and passes them to the function body. However, there are times when you do not want function arguments evaluated. For instance, if you need to pass a word and access it from the function body, you do not want it evaluated as an argument. The help function, which expects a word, is a good example:
help print
To prevent print from being evaluated, the help function must specify that its argument should not be evaluated.
To specify that an argument not be evaluated, precede the argument name with a single quote (indicates a literal word). For example:
zap: func [`var] [set var 0]
test: 10
zap test
print test
10
The var argument is preceded with a single quote, which instructs the interpreter to obtain the argument without evaluating it first. The argument is passed as the word. For example:
say: func [`var] [probe var]
say test
test
The example prints the word that is passed as an argument.
Another example is a function that increments a variable by one and returns its result (similar to the ++ increment function in C):
++: func ['word] [set word 1 + get word]
count: 0
++ count
print count
1
print ++ count
2
Function arguments can also specify that a word's value be fetched but not evaluated. This is similar to the literal arguments described above, but rather than passing the word, the value of the word is passed without being evaluated.
To specify that an argument be fetched but not evaluated, precede the argument name with a colon. For example, the following function accepts functions as arguments:
print-body: func [:fun] [probe second :fun]
The sample function prints the body of a function that is passed to it. The argument is preceded by a colon, which indicates that the value of the word should be obtained, but not further evaluated.
print-body reform
[form reduce value]
print-body rejoin [ if empty? block: reduce block [return block] append either series? first block [copy first block] [ form first block] next block ]
Refinements can be used to specify variation in the normal evaluation of a function as well as provide optional arguments. Refinements are added to the function specification block as a word preceded by a forward slash (/).
Within the body of the function, the refinement word is used as a logic value to determine if the refinement was provided when the function was called.
For example, the following code adds a refinement to the sum function, which was defined in a previous example:
sum: func [ "Return the sum of two numbers." arg1 [number!] "first number" arg2 [number!] "second number" /average "return the average of the numbers" ][ either average [arg1 + arg2 / 2][arg1 + arg2] ]
The sum function specifies the /average refinement. In the body of the function, the word is tested with the either function, which returns true when the refinement is specified.
print sum/average 123 321
222
To specify a refinement that accepts additional arguments, follow the refinement with the arguments definitions:
sum: func [ "Return the sum of two numbers." arg1 [number!] "first number" arg2 [number!] "second number" /times "multiply the result" amount [number!] "how many times" ][ either times [arg1 + arg2 * amount][arg1 + arg2] ]
The amount is only valid when the times refinement is true. Here is an example:
print sum/times 123 321 10
4440
Do not forget to check the refinement word before using the additional arguments. If a refinement argument is used without the refinement being specified, it will have a none! value.
A local variable is a word whose value is defined within the scope of a function. Changes to a local variable only affect the function in which the variable is defined. If the same word is used outside of the function, it will not be affected by the changes to the local variable of the same name.
Argument variables and refinements are local variables. Their values are defined within the scope of the function. By convention, additional local variables can be specified with the /local refinement. The /local refinement is followed by a list of words that are used as local variables within the function.
average: func [ block "Block of numbers" /local total length ][ total: 0 length: length? block foreach num block [total: total + num] either length > 0 [total / length][0] ]
Here the total and length words are local to the function.
Another method of creating local words is to use the function! function, which is identical to func, but accepts a separate block that contains the local words:
average: function [ block "Block of numbers" ][ total length ][ total: 0 length: length? block foreach num block [total: total + num] either length > 0 [total / length][0] ]
In this example, notice that the /local refinement is not used with the function! function. The function! function creates the refinements for you.
If a local variable is used before its value has been set within the body of its function, it will have a none! value.
Local variables that hold series need to be copied if the series is used multiple times. For example, if you want the stars string to be the same each time you call the start-name function, you should write:
star-name: func [name] [ stars: copy "**" insert next stars name stars ]
Otherwise, if you write:
star-name: func [name] [ stars: "**" insert next stars name stars ]
you will be using the same string each time and each time the function is used the pervious name will appear within the result.
print star-name "test"
*test*
print star-name "this"
*thistest*
As you know from the [bad-link:concepts/expressions.txt] Chapter, blocks return their last value when they return from evaluation:
do [1 + 3 5 + 7]
12
This is also true for functions. The last value is returned as the value of the function:
sum: func [a b] [ print a print b a + b ] print sum 123 321 123 321 444
In addition, the return function can be used to stop the evaluation of a function at any point and return a value:
find-value: func [series value] [
forall series [
if (first series) = value [
return series
]
]
none
]
probe find-value [1 2 3 4] 3
[3 4]
In the example, if the value is found, the function returns the series at the position of the match. Otherwise, the function returns none!.
To stop a function evaluation without returning a value, use the exit function:
source: func [ "Print the source code for a word" 'word [word!] ][ prin join word ": " if not value? word [print "undefined" exit] either any [ native? get word op? get word action? get word ][ print ["native" mold third get word] ][print mold get word] ]
To return more than one value from a function, use a block. You can do this easily by returning a block that has been reduced.
For example:
find-value: func [series value /local count] [ forall series [ if (first series) = value [ reduce [series index? series] ] ] none ]
The function returns a block that holds the series and the index value where the value was found.
probe find-value [1 2 3 4] 3
[[3 4] 3]
The reduce is necessary to create a block of values from the block of words that it is given. Do not return the local variables themselves. That is not a supported mode of operation (currently).
To easily set variables to the return value of the function, use set:
set [block index] find-value [1 2 3 4] 3
print block
3 4
print index
3
TOC < Back Next > | REBOL.com - WIP Wiki | Feedback Admin |