Functional Metaprogramming
Functional metaprogramming allows for the dynamic creation and manipulation of function definitions, enabling functions to be defined, modified, or executed during runtime. This approach simplifies the process of defining functions on the fly.
Obtaining Function Definitions Dynamically
In DolphinDB, a function definition is represented by the data type FUNCTIONDEF. You
can use the built-in function funcByName
to dynamically retrieve a
function definition based on the its name.
funcByName
to obtain function definition of sin based on its
name stored in name. Then you can pass v to the function for
calculation.name = `sin
v = 1..10
funcByName(name)(v)
// output: [0.8415,0.9093,0.1411,-0.75688,-0.9589,-0.2794,0.657,0.9894,0.4121,-0.5440]
::
.There are scenarios where you might need to work with anonymous functions (including
lambda expressions). To handle these cases, you can pass the function definition as
a string variable to parseExpr
. ParseExpr
returns
the metacode, which can be evaluated with eval
.
funcDef = "x->1 + x + x*x"
parseExpr(funcDef).eval().call(2)
//output: 7
Calling Dynamically-Generated Functions
call
, the operator ()
, and the
at
function.f = parseExpr("{x,y->(x - y)/(x + y)}").eval()
// method 1: using the call function
call(f, 3.0, 2.0)
// method 2: using operator ()
f(3.0, 2.0)
// method 3: using the at function
at(f, (3.0, 2.0))
The first two methods, call
and ()
, are similar in
that they accept a variable number of arguments, requiring that each argument of the
target function be individually provided during invocation.
The at
function, however, always takes exactly two arguments: the
function definition and the argument(s) needed for the function. This fixed
two-argument syntax offers advantages in dynamic invocation scenarios, reducing
complexity and potential errors. When calling a function requiring multiple
arguments, the second argument of the at
function must be a tuple,
with each element representing one of the function's inputs.
A special case arises when the function being called is a unary function that itself
takes a tuple as its single input. In this case, to use the at
function correctly, you must use the enlist
function to wrap the
input tuple as a new tuple with the original tuple as its sole element. Without this
step, the system would interpret the elements of the original tuple as separate
arguments, leading to an error.
f = {x->x.head()\x.tail()}
x = (1,2,3)
// Reports error: The function [] expects 1 argument(s), but the actual number of arguments is: 3
at(f, x)
// correct syntax
at(f, enlist x)
Generating Function Calls Dynamically
While the dynamic function calls discussed earlier execute immediately and return a value, there are scenarios where we need to create code containing dynamic function calls for later execution, such as defining filtering conditions with functions in SQL queries.
To facilitate this, DolphinDB provides two specialized functions:
makeCall
and makeUnifiedCall
. These functions
generate code for function calls that can be used in lazy execution.
The key difference between them mirrors the distinction between the
call
and at
functions:
makeCall
requires you to input arguments based on the number
needed by the corresponding function. makeUnifiedCall
requires a
fixed number of arguments. If the function requires multiple arguments, they are
encapsulated in a tuple.
- Using
makeCall
: UsesqlCol
to generate metacode for each input column. - Using
makeUnifiedCall
: a. Manually create a tuple with each element generated withsqlCol
, or b. usesqlTuple
to generate metacode with a tuple expression.
f = parseExpr("{x,y->(x - y)/(x + y)}").eval()
makeCall(f, sqlCol("qty1"), sqlCol("qty2"))
makeUnifiedCall(f, (sqlCol("qty1"), sqlCol("qty2")))
makeUnifiedCall(f, sqlTuple(`qty1`qty2))
makeCall
/makeUnifiedCall
can
be passed as the parameter select of function sql
to
generate SQL dynamically. The selected columns will be processed with
f
.f = parseExpr("{x,y->(x-y)/(x+y)}").eval()
t = table(1.0 2.0 3.0 as qty1, 1.0 3.0 7.0 as qty2)
sql(select=makeCall(f, sqlCol("qty1"), sqlCol("qty2")), from=t).eval()
sql(select=makeUnifiedCall(f, (sqlCol("qty1"), sqlCol("qty2"))), from=t).eval()
sql(select=makeUnifiedCall(f, sqlTuple(`qty1`qty2)), from=t).eval()
_qty1 |
---|
0 |
-0.2 |
-0.4 |