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

Feature request: UNKNOWN type #666

Open
Virviil opened this issue Jun 2, 2024 · 6 comments
Open

Feature request: UNKNOWN type #666

Virviil opened this issue Jun 2, 2024 · 6 comments
Labels

Comments

@Virviil
Copy link

Virviil commented Jun 2, 2024

I want to perform some dry-run calculations with dirty custom functions.
Sometimes i don't know the value that will be in real run, but i know that this value will be.

I cant use nil or any another value, because it can change the attitude of an expression.
For example foo ?? download(bar) , when foo is nil will force to download bar while it is not what's desired if foo is definitely known to be not nil

My proposal is to add type UNKNOWN and UNKNOWN_NOT_NIL, which will during computation propogate it's unknoness. These values can be used as any other type. At the same time, calling dirty custom functions can check if the value is UNKNOWN and perform accordingly.

Predefined functions, while being pure, can just return UNKNOWN as there result. Important to calculate UNKNOWN <-> UNKNOWN_NOT_NIL conversions properly.

This change is fully backward compatible, because not giving any UNKNOWN value to env will just work as is.

@Virviil Virviil changed the title Feature request: Feature request: UNKNOWN type Jun 2, 2024
@antonmedv
Copy link
Member

Those unknow for the run step?

@Virviil
Copy link
Author

Virviil commented Jun 2, 2024

@antonmedv Yep.

Let's say, each download cost money, so i want to dry-run the expression and tell, how much will it cost.

The problem is that I want to chain some expr's, so second expression can be done only after first, which can result in different values for second one.

This problem is quite unsolvable, but at least I want to calculate the ceiling of price for these downloads.

The problem seems to be a hard one because the ub for each opcode that returns boolean.

For example, is IsEqual(unknown, unknown) probably should return unknown, but it will definitely break the control flow. So, probably let's say IsEqual(unknown, unknown) should return false...

@antonmedv
Copy link
Member

I actually just finished a PR which brings unknown values to the type checker #665 See bunch of return unknown. And those types will work on type checker step.

What you can do to estimate upper bound is using visitor traverse AST of the expression and count how many times download() function were called.

Take a look on, for example, on visitor which collects names of all used variables: https://expr-lang.org/docs/visitor

@Virviil
Copy link
Author

Virviil commented Jun 2, 2024

@antonmedv Can you please share example to use these unknowns?

Unfortunately counting "download" function in program code is not giving any information because it can be behind "if" not used branch (so no need to count it) or inside "map" (which can be called multiple times)

@mei-rune
Copy link

mei-rune commented Jun 4, 2024

I hope add a dynamic type, this expression is invalid as follows:
我希望增加一个 dynamic type, 当前下面的表达式是错误的:

out, err := expr.Compile(`value1 + value2`)
// err: invalid operation + (mismatched types string and int)
// | name + age
// | .....^

I hope add a option to enable dynamic support ( backward compatible while disable dynamic),
我希望增中一个选项来开启 dynamic type 的支持

program, err := expr.Compile(`value1 + value2`, EnableDynamicType())
// ok

output, err := expr.Run(program, map[string]interface{}{
   "value1": 1,
   "value2": 2,
})
// ok, err == nil  and output = 3

output, err := expr.Run(program, map[string]interface{}{
   "value1": 1.1,
   "value2": 2,
})
// ok, err == nil  and output = 3.1

output, err := expr.Run(program, map[string]interface{}{
   "value1": "1",
   "value2": "2",
})
// ok, err == nil  and output = 12

output, err := expr.Run(program, map[string]interface{}{
   "value1": 1,
   "value2": "2",
})
// panic, err != nil

@antonmedv
Copy link
Member

@Virviil those are internal type checker structs.

I'm actually working on something very similar: estimated expression costs. You can assign a cost for each field/array/function and Expr will calculate estimated cost:

download() = 100
arr = 10
map(arr, download(#)) + map(arr, download(#))

Estimated cost will be: 10 * 100 + 10 * 100

Maybe it makes sense to add "units" to this system, so we can calculate in term of different units.

download() = 100d

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

No branches or pull requests

3 participants