JuliaSymbolics/Metatheory.jl

Predicates for pattern variables are not checked consistently

Opened this issue · 3 comments

I was surprised by some inconsistent behaviour of pattern variable predicates (when using the same pattern variable).
If a pattern variable occurs twice, it seems that only the predicate for the first occurrance is checked.
Here is a minimal example:

using Metatheory

is_nonzero(::EGraph, ::EClass) = false # just for the test

theory1 = @theory a begin
  a / a --> 1
end

theory2 = @theory a begin
  a::is_nonzero / a --> 1
end

theory3 = @theory a begin
  a / a::is_nonzero --> 1
end

theory4 = @theory a begin
  a::is_nonzero / a::is_nonzero --> 1
end

function test(theo)
  g = EGraph(:(x / x))
  saturate!(g, theo)
  extract!(g, astsize)
end

test(theory1) # --> 1 (as expected)
test(theory2) # --> :(x / x) (as expected because the predicate works)
test(theory3) # --> 1 (incorrect! predicate only used for the second variable)
test(theory4) # --> :(x / x) ok

There is also no warning about inconsistent predicates. People might use different predicates for different occurances of pattern variables.

Related:

MT supports rule predicates for dynamic rules for example:

   ~a * ~b => 0 where (iszero(a) || iszero(b))

where iszero is a function that takes an EClass as argument. This is implemented as syntactic sugar. The rule is simply transformed to

  ~a * ~b => (iszero(a) || iszero(b)) ? 0 : nothing

It would be great to allow the same pattern for directed rewrite rules as well.

   ~a * ~b --> 0 where (iszero(a) || iszero(b))

But that would probably require handling the predicate in the compiled matching function.

But that would probably require handling the predicate in the compiled matching function.

Yes indeed

@gkronber

This suggests that we probably need a nicer way to attaching predicates to variables. Probably a per-scope dictionary in patterns:

When parsing pattern

  a / a::is_nonzero

The issue is that the predicate is attached only to the second variable, and not to the first one.
In order to solve this issue, we need some way of re-walking the pattern after it's parsed and attach the correct predicates to each variables.

When parsing predicate we could use a Dict{Int, Predicate} (Int is OK due to debruijn indexing of variables) where Predicate = Union{Function, DataType} or some struct for performance improvements due to runtime dispatch