Named Function
A function is a group of statements that is executed when called. It returns a value or multiple values. In DolphinDB, system built-in functions are not allowed to be overridden.
Both built-in functions and user-defined functions support specifying default parameter values. Note that the parameter with a default value cannot be mutable, and the default value must be a constant. All parameters after a parameter that is configured with a default value must also be configured with default values as well.
Note: When using mutable for a parameter in an outer function, all
nested functions within it using the same parameter must also be decorated with
mutable. Otherwise an error may occur during multi-threaded
processing: Read only object can't be applied to mutable function
xxx.
Syntax
def <functionName> ([parameters]) {statements}
or
def <functionName> ([parameters]): statement (can only have one statement here)
A named function can include a description, argument type annotations, and a return type annotation:
@desc("<description>")
def <functionName>(<argument>::<typeAnnotation>, ...)::<returnAnnotation> {statements}
or
@desc("<description>")
def <functionName>(<argument>::<typeAnnotation>, ...)::<returnAnnotation>: statement
We will discuss user-defined aggregate functions at the end of this section. Their syntax is the same as the named functions except that their definitions start with "defg" instead of "def".
Descriptions and Type Annotations
@desc records the function description. Descriptions, argument type
annotations, and return type annotations are all optional and can be used
independently.
-
When a function also uses annotations such as
@state,@transform, or@jit, place@desclast so that it is immediately before the function definition. -
Argument and return type annotations only describe the function signature and pass metadata to external tools. They do not validate the actual argument or return types or data forms.
-
Type annotations can be used together with default arguments and
mutablearguments.
Type annotations follow these rules:
| Data Form | Format | Example |
|---|---|---|
| Scalar | <TYPE> |
INT, DECIMAL64(4) |
| Pair, vector, matrix, or set | <TYPE> <FORM> |
INT VECTOR, DOUBLE MATRIX |
| Array vector | <TYPE>[] VECTOR |
INT[] VECTOR |
| Columnar tuple | ANY[<TYPE>] VECTOR |
ANY[INT] VECTOR, ANY[DECIMAL32(2)] VECTOR |
| Dictionary | DICT[<KEY_TYPE>, <TYPE>] |
DICT[STRING, DOUBLE] |
| Other data forms | <FORM> |
TABLE |
Do not include SCALAR in a scalar type annotation.
FUNCTIONDEF, HANDLE,
DATASOURCE, and RESOURCE cannot be used as
type annotations.
In the 3.0 series, a dictionary value annotation can be a nested
complex type, such as DICT[STRING, DOUBLE MATRIX].
@state
@desc("Calculate the cumulative maximum.")
def cumulativeMax(x::DOUBLE VECTOR)::DOUBLE VECTOR {
return cummax(x)
}
Function parameters
-
Function parameters are always passed by references.
-
An input parameter can be modified within function body if and only if it is qualified by "mutable".
Examples
Define a named function:
def f(a){return a+1};
f(2);
// output: 3
def f(a):a+1;
f(3);
// output: 4
def f(a=1, b=2){return a + b}
f(,3)
// output: 4
Define a named function with a description, argument type annotations, and a return type annotation:
@desc("Add two vectors and return a DOUBLE vector.")
def addVectors(a::INT VECTOR, b::DOUBLE VECTOR)::DOUBLE VECTOR {
return double(a) + b
}
addVectors(a=1 2 3, b=0.5 1.5 2.5)
// output: [1.5,3.5,5.5]
Assign a function or an array of functions to variables:
g=sin;
g(3.1415926);
// output: 5.358979e-008
g=[sin, cos, log];
g(1 2);
| sin | cos | log |
|---|---|---|
| 0.841471 | 0.540302 | 0 |
| 0.909297 | -0.416147 | 0.693147 |
If a function and a variable have the same name, we can use the address operator (&) to indicate a function.
sum=15;
g=sum;
g;
// output: 15
g=∑
g;
// output: sum
g(1..4);
// output: 10
Immutable parameters cannot be modified within the function:
def f(a){a+=1; return a};
// output: Syntax Error: [line #1] Constant variable [a] can't be modified.
Mutable parameters can be modified within the function:
def f(mutable a){a+=1; return a};
x=1;
f(x);
// output: 2
f(x);
// output: 3
x;
// output: 3
x=1..6$3:2;
x;
| #0 | #1 |
|---|---|
| 1 | 4 |
| 2 | 5 |
| 3 | 6 |
def f(mutable a){if(a.rows()>=2 && a.cols()>=2){a[1,1]=0}; return a};
f(x);
| #0 | #1 |
|---|---|
| 1 | 4 |
| 2 | 0 |
| 3 | 6 |
Declare a function first then define it later:
def f2(a,b)
def f1(a,b){return f2(a,b)}
def f2(a,b){a pow b};
// ";" should not be put in-between the lines otherwise the system cannot tell f2 is a declaration without the follow up definition.
f1(2,3);
// output: 8
Return multiple values:
def summary(a){return sum(a), avg(a), std(a)};
x=1..11;
summary(x);
// output: [66, 6, 3.316625]
def factorial(n)
def factorial(n){
if(n <= 1) return 1
return n * factorial(n - 1)
}
factorial(5)
// output: 120Note: Recursive functions do not support calling asynchronous execution functions
within the function body, such as submitJob,
submitJobEx, and submitJobEx2; otherwise, the
node may crash.
A more complicated example:
Let's write a function to calculate the covariance between input vectors of equal length.
(1) If neither a nor b has null elements, calculate the covariance between them.
(2) If either a or b contains null elements, we first retrieve the sub vectors that do not contain any null elements and then calculate the covariance.
def calcovar(a, b){
aNull=hasNull a; // return true if input vector a contains null values; otherwise, false
bNull=hasNull b;
if(!aNull && !bNull){ // if neither a or b contains null values
am=avg a; // calculate the mean of input vector a using function avg
bm=avg b; // calculate the mean of input vector b using function avg
ab=a ** b; // calculate the inner product of vector a and b
n=count a; // get the number of non-null values
return (ab-n*am*bm) \ (n-1); // return a covariance value
}
else{ // get all positions in which values are not null in both a and b
if(!aNull) // if a does not contain any null values
index=!isNull b; // get the indices of non-null values in b
else {
if(!bNull) // if b does not contain any null values
index=!isNull a; // get the indices of non-null values in a
else
index=!((isNull a) || (isNull b)); // get the positions that are not nulls in both a and b
}
c=a[index];
d=b[index];
am=avg c;
bm=avg d;
ab=c ** d;
n=count c;
return (ab-n*am*bm) \ (n-1);
}
}
User-defined Aggregate Functions
A user-defined aggregate function is a function that restricts the data form of the output to be scalar. There are times when we would like to make sure a function returns a scalar. Aggregate functions serve this purpose.
User-defined aggregate functions have the same syntax as the named functions except that their definitions start with "defg" instead of "def".
defg f(x, y){
a = sum(abs(x+y))
b=sum(abs(x))+sum(abs(y))
return a\b
};
x=-5..5; y=0..10;
f(x,y);
// output: 0.858824
