Pattern-matching with Flat Attribute: Different from Mathematica
Please consider
SetAttributes[eqv, Flat]
eqv[p, q, q, p] /. eqv[x_, y_] :> {x, y}
which produces
{q, eqv[q, q, p]}
in mathics, and
{eqv[q], eqv[q, q, p]}
in Mathematica. This has ramifications in symbolic manipulations with custom associative operators because the missing head eqv is observable in semantics layers.
I believe that the bigger issue, here, is correct modeling of idempotent functions with a combination of Flat and OneIdentiy
Idempotent functions have the behavior eqv[eqv[p]]===eqv[p]===p, e.g., Times[Times[x]]===Times[x]===x.
https://reference.wolfram.com/language/ref/OneIdentity.html?view=all .
This isn't completely clear to me, yet, but I will write some tests in Mathematica today and say more later. But the following crashes mathics now:
In[7]:= SetAttributes[or,{OneIdentity,Orderless}]
In[12]:= or[p,p,p]/.or[p_.,p_.]:>p
File "mathics/core/expression.py", line 111, in mathics.core.expression.from_python (mathics/core/expression.c:6658)
raise NotImplementedError
Looks like mathics assumes OneIdentity. Let's consider Mathematica's built-in Or, first. Its relevant attributes are just Flat, OneIdentity:
In[97]:= Attributes[Or]
Out[97]= {Flat, HoldAll, OneIdentity, Protected}
It also has no Default:
In[99]:= Default[Or]
Out[99]= Default[Or]
That produces the following behavior:
In[107]:= Or[p, p, p] /. Or[a_, b_] :> {a, b}
Or[p, p, p] /. Or[a_., b_.] :> {a, b}
Out[107]= {p, p || p}
Out[108]= {p, p} || p
In[109]:= Or[p, p, p]
Out[109]= p || p || p
In[112]:= Or[p, q] === Or[q, p]
Out[112]= False
This is similar to, but not exactly the same, as mathics; Pattern produces a nodef message by side effect when presented with Optional[Blank[]] patterns (syntax: _., underscore-dot) and reduces as if the patterns were not optional.
In[9]:= Or[p, p, p] /. Or[a_, b_] :> {a, b}
Out[9]= {p, p || p}
In[10]:= Or[p, p, p] /. Or[a_., b_.] :> {a, b}
Pattern::nodef: No default setting found for Or in position 1 when length is 2.
Out[10]= {p, p || p}
In[11]:= Or[p, p, p]
Out[11]= p || p || p
In[12]:= Or[p, q] === Or[q, p]
Out[12]= False
We want our or to be symmetric, so we will add Orderless. First, here is what Mathematica says, in stages, adding the attributes one at a time:
In[43]:= ClearAll[or]; SetAttributes[or, {Flat}]
or[p, p, p] /. or[a_, b_] :> {a, b}
or[p, p, p] /. or[a_., b_.] :> {a, b}
Out[44]= {or[p], or[p, p]} <~~~ THIS WAS THE FIRST TOPIC OF THIS ISSUE
Out[45]= or[{p, p}, p]
In[40]:= ClearAll[or]; SetAttributes[or, {Flat, OneIdentity}]
or[p, p, p] /. or[a_, b_] :> {a, b}
or[p, p, p] /. or[a_., b_.] :> {a, b}
Out[41]= {p, or[p, p]} <~~~ MATHEMATICA SAYS "ONLY IF ONEIDENTITY"
Out[42]= or[{p, p}, p]
In[46]:= ClearAll[or]; SetAttributes[or, {Flat, Orderless, OneIdentity}]
or[p, p, p] /. or[a_, b_] :> {a, b}
or[p, p, p] /. or[a_., b_.] :> {a, b}
Out[47]= {p, or[p, p]} <~~~ THIS IS OK TOO
Out[48]= or[p, p, p]
Mathics disagrees right away:
In[13]:= ClearAll[or]; SetAttributes[or, {Flat}]
In[14]:= or[p, p, p] /. or[a_, b_] :> {a, b}
Out[14]= {p, or[p, p]}
In[15]:= or[p, p, p] /. or[a_., b_.] :> {a, b}
Pattern::nodef: No default setting found for or in position 1 when length is 2.
Out[15]= {p, or[p, p]}
In[16]:= ClearAll[or]; SetAttributes[or, {Flat, OneIdentity}]
In[17]:= or[p, p, p] /. or[a_, b_] :> {a, b}
Out[17]= {p, or[p, p]}
In[18]:= or[p, p, p] /. or[a_., b_.] :> {a, b}
Pattern::nodef: No default setting found for or in position 1 when length is 2.
Out[18]= {p, or[p, p]}
In[19]:= ClearAll[or]; SetAttributes[or, {Flat, Orderless, OneIdentity}]
In[20]:= or[p, p, p] /. or[a_, b_] :> {a, b}
Out[20]= {p, or[p, p]}
In[21]:= or[p, p, p] /. or[a_., b_.] :> {a, b}
Out[21]= {p, or[p, p]}
Here is some more data about differences between Mathematica and mathics. Mathics has the correct pattern-matching behavior for Times, but doesn't let us mimic the behavior in our own symbol. I think mathics is confused about OneIdentity and implements the correct behavior for Times in some other way.
Here is what Mathematica says:
In[124]:= x /. Times[n_., x_] :> {n, x}
ClearAll[or]; SetAttributes[or, {Flat, Orderless, OneIdentity}]
Default[or] = False;
p /. or[n_., x_] :> {n, x}
Out[124]= {1, x}
Out[127]= {False, p}
and now mathics:
In[1]:= x /. Times[n_., x] :> {n, x}
Out[1]= {1, x}
In[2]:= SetAttributes[or, {Flat, Orderless, OneIdentity}]
In[3]:= Default[or] = False
In[4]:= p /. or[n_., x] :> {n, x}
Out[4]= p <~~~ NO MATCH
More data:
in both mathics
and Mathematica,
First[eqv[p, q, r]] === p
Rest[eqv[p, q, r]] === eqv[q, r]]
for all combinations of {Flat, Orderless, OneIdentity}. Proof:
Generate all combinations as follows:
In[66]:=
Flatten[
Table[
Union[
Sort/@Permutations[{Flat,Orderless,OneIdentity}, {i}]],
{i,3}],
1]
Out[66]= {{Flat},
{OneIdentity},
{Orderless},
{Flat, OneIdentity},
{Flat, Orderless},
{OneIdentity, Orderless},
{Flat, OneIdentity, Orderless}}
Then check as follows:
In[67]:=
Table[
Module[{e = eqv[p,q,r]}, <~~~ HERE IS THE MONEY
ClearAll[eqv];
SetAttributes[eqv,j];
{j, First@e, Rest@e}],
{j, ... the above ...}]
Out[67]= {{{Flat}, p, eqv[q, r]},
{{OneIdentity}, p, eqv[q, r]},
{{Orderless}, p, eqv[q, r]},
{{Flat, OneIdentity}, p, eqv[q, r]},
{{Flat, Orderless}, p, eqv[q, r]},
{{OneIdentity, Orderless}, p, eqv[q, r]},
{{Flat, OneIdentity, Orderless}, p, eqv[q, r]}}
We only get differences when we pattern-match:
Table[
Module[{ e = (eqv[p,q,r] /. {eqv[x_,y_] :> {x,y}}) }, <~~~ DIFF'T
ClearAll[eqv];
SetAttributes[eqv,j];
{j, First@e, Rest@e}],
{j, ... the above ...}]
produces, in Mathematica
{{{Flat}, p, {eqv[q, r]}},
{{OneIdentity}, eqv[p], {eqv[q, r]}},
{{Orderless}, p, eqv[q, r]},
{{Flat, OneIdentity}, p, eqv[q, r]},
{{Flat, Orderless}, p, {eqv[q, r]}},
{{OneIdentity, Orderless}, q, {eqv[p, r]}},
{{Flat, OneIdentity, Orderless}, p, eqv[q, r]}}
but, in mathics
{{{Flat}, p, {eqv[q, r]}},
{{OneIdentity}, p, {eqv[q, r]}},
{{Orderless}, p, eqv[q, r]},
{{Flat, OneIdentity}, p, eqv[q, r]},
{{Flat, Orderless}, p, {eqv[q, r]}},
{{OneIdentity, Orderless}, p, {eqv[q, r]}},
{{Flat, OneIdentity, Orderless}, p, eqv[q, r]}}
I have some open questions on mathematica.stackexchange.com about this issue.
https://mathematica.stackexchange.com/questions/183322/subtle-order-of-evaluation-issues-when-pattern-matching-with-attributes
I regret that this became complicated, but I hope to get a much better understanding of the way pattern-matching is supposed to work if some people inside Wolfram look at the question. I know at least two employees (Lichtblau and Shifrin) watch that site.
EDIT
The issue has been resolved in stackexchange. Please see my answer on that site for suggested unit tests for mathics.
I've started working on this again as of 11 Oct 2020. No ETA.