Functions
Expr comes with a set of builtin functions, but you can also define your own functions.
The easiest way to define a custom function is to add it to the environment.
env := map[string]any{
"add": func(a, b int) int {
return a + b
},
}
Or you can use functions defined on a struct:
type Env struct{}
func (Env) Add(a, b int) int {
return a + b
}
If functions are marked with ConstExpr
option, they will be evaluated at compile time.
The best way to define a function from a performance perspective is to use a Function
option.
atoi := expr.Function(
"atoi",
func(params ...any) (any, error) {
return strconv.Atoi(params[0].(string))
},
)
program, err := expr.Compile(`atoi("42")`, atoi)
Type checker sees the atoi
function as a function with a variadic number of arguments of type any
, and returns
a value of type any
. But, we can specify the types of arguments and the return value by adding the correct function
signature or multiple signatures.
atoi := expr.Function(
"atoi",
func(params ...any) (any, error) {
return strconv.Atoi(params[0].(string))
},
new(func(string) int),
)
Or we can simply reuse the strconv.Atoi function as a type:
atoi := expr.Function(
"atoi",
func(params ...any) (any, error) {
return strconv.Atoi(params[0].(string))
},
strconv.Atoi,
)
It is possible to define multiple signatures for a function:
toInt := expr.Function(
"toInt",
func(params ...any) (any, error) {
switch params[0].(type) {
case float64:
return int(params[0].(float64)), nil
case string:
return strconv.Atoi(params[0].(string))
}
return nil, fmt.Errorf("invalid type")
},
new(func(float64) int),
new(func(string) int),
)