Functions defined on the struct are bidden to the Env they were compiled with
For example, the following program can't be reused with different environments, because functions are bidden to the Env they were compiled with:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
type Env struct {
Value string
}
func (e *Env) Get(args ...any) (any, error) {
return e.Value, nil
}
func main() {
code := `get()`
env := Env{Value: "<empty>"}
program, err := expr.Compile(code,
expr.Function("get", env.Get, new(func() any)),
expr.Env(env))
if err != nil {
panic(err)
}
output, err := expr.Run(program, Env{Value: "hello"})
if err != nil {
panic(err)
}
fmt.Println(output)
output, err = expr.Run(program, Env{Value: "world"})
if err != nil {
panic(err)
}
fmt.Println(output)
}
The program output:
<empty>
<empty>
Expected:
hello
world
I can patch the code to pass $env as the first arg of the function, but then users get weird error messages if compilation fails.
This is intended behaviour. You passed env.Get to Function:
expr.Function("get", env.Get, new(func() any)),
In Golang this makes reciver argument bidden to env. You can simplify your code and do not use expr.Function and use methods on env directly.
Please, try this approach and see if it works.
You can simplify your code and do not use expr.Function and use methods on env directly.
But it will be slower and I can't use type hints to get helpful error message, right? It is not the trade-off I am willing to make...
It will be just a bit slower. Types will be inherited.
If I understood correctly, what you trying to achieve you can try this approach: create a function which will be bidden to and env outside of expr. Use expr.Function to call it.
This way you can achieve fastest calling speed and preserve bindings.
I will try to write some code when I can reach my computer
The program I've shared gives a good example of what I am trying to achieve:
- Compile a program once and then run it with different struct envs.
- Functions must have access the current/running env, because I don't have vars and use getter functions instead. It is much more flexible this way.
Currently I am patching functions to pass $env as the first arg. It would be nice if expr-lang could detect this and pass it automatically, i.e. support functions with this signature:
var funcGetAttr = expr.Function(
"getAttr",
func (env expr.Env, args ...any) (any, error) {
fmt.Println(env.(*myenv), args[0].(string))
},
new(func(string) string),
)
This is a nice idea. Expr already has patcher for context. We can have a patcher for env as well.