expr icon indicating copy to clipboard operation
expr copied to clipboard

Feature request: UNKNOWN type

Open Virviil opened this issue 1 year ago • 6 comments

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 avatar Jun 02 '24 09:06 Virviil

Those unknow for the run step?

antonmedv avatar Jun 02 '24 18:06 antonmedv

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

Virviil avatar Jun 02 '24 18:06 Virviil

I actually just finished a PR which brings unknown values to the type checker https://github.com/expr-lang/expr/pull/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

antonmedv avatar Jun 02 '24 19:06 antonmedv

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

Virviil avatar Jun 02 '24 20:06 Virviil

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

mei-rune avatar Jun 04 '24 06:06 mei-rune

@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

antonmedv avatar Jun 04 '24 13:06 antonmedv

I've added support for unknown types. Now checker's nature has unknown support. If you specify expr. AllowUndefinedVariables() all unknown wars will be unknown!

Also we now have https://github.com/expr-lang/expr/tree/master/types and maybe we will introduce new unknown type there.

antonmedv avatar Mar 11 '25 08:03 antonmedv