Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Functions defined on the struct are bidden to the Env they were compiled with #695

Open
vmihailenco opened this issue Aug 9, 2024 · 6 comments

Comments

@vmihailenco
Copy link

vmihailenco commented Aug 9, 2024

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.

@antonmedv
Copy link
Member

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.

@vmihailenco
Copy link
Author

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...

@antonmedv
Copy link
Member

It will be just a bit slower. Types will be inherited.

@antonmedv
Copy link
Member

antonmedv commented Aug 10, 2024

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

@vmihailenco
Copy link
Author

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),
)

@antonmedv
Copy link
Member

This is a nice idea. Expr already has patcher for context. We can have a patcher for env as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants