PatternTest没有优化?

Mr.*_*ard 8 wolfram-mathematica pattern-matching

在准备对Mathematica中PatternTest的意外行为的响应时,我遇到了一个意想不到的Mathematica行为.

请考虑:

test = (Print[##]; False) &;
MatchQ[{1, 2, 3, 4, 5}, {x__?test, y__}]
Run Code Online (Sandbox Code Playgroud)
During evaluation of In[1]:= 1

During evaluation of In[1]:= 1

During evaluation of In[1]:= 1

During evaluation of In[1]:= 1
False

因为正如西蒙对文献的引用所简明地指出:

在一个形式中,例如__?test匹配的序列中的每个元素__ 必须True在应用测试时产生.

我想知道为什么Mathematica四次分别测试列表的第一个元素.当然,有四种方法可以构成底层模式{x__, y__},如果这是一个Condition测试,那么组成序列的所有元素x都需要进行测试,但我不认为这是这种情况.

逻辑是否认为如果列表的第一个元素失败,PatternTest那么给定的模式不能匹配?

如果它确实成立,为什么Mathematica不进行这种简单的优化?


借用尤达的回答中的一个例子,这是另一个看似过度评价的例子:

In[1]:= test2 = (Print@##; Positive@##) &;
MatchQ[{1, 2, 3, 4, -5}, {x__?test2, y__?Negative}]

During evaluation of In[1]:= 1

During evaluation of In[1]:= 1

During evaluation of In[1]:= 2

During evaluation of In[1]:= 1

During evaluation of In[1]:= 2

During evaluation of In[1]:= 3

During evaluation of In[1]:= 1

During evaluation of In[1]:= 2

During evaluation of In[1]:= 3

During evaluation of In[1]:= 4

Out[2]= True
Run Code Online (Sandbox Code Playgroud)

我承认以前从未探索过模式匹配的这个方面,而我对此看似无效率感到不安.这真的和它看起来一样糟糕,还是发生了某种自动缓存? Print似乎表明反对.

  • 是否有更有效的基于模式的方式来写这个?

  • 正确的模式匹配行为需要这种冗余级别,为什么?


我匆忙做了一个错误的断言,但是我离开了,因为下面的好答案解决了它.请在将来的答案中忽略它.

很容易证明在其他情况下进行了类似的优化:

MatchQ[{-1, 2, 3, 4, 5}, {__?Positive, y__?test}]
Run Code Online (Sandbox Code Playgroud)

(没有打印.)

False

在这里,Mathematica正确地从未测试任何元素y.

Leo*_*rin 7

我想每个人都忘记了测试功能可能产生的副作用.以下是我认为正在发生的事情:正如Mr.Wizard和其他人所提到的,模式可以通过组合方式进行匹配.对于的每个组合{x}{y},所述x图案被首先测试.顺便说一句,定义多个arguments(##)的函数是没有意义的,因为正如@Simon解释的那样,测试函数被单独应用于序列中的每个元素.这也解释了为什么只打印第一个元素(-1):一旦找到第一个非匹配元素,模式匹配器就会停止并继续测试下一个可用组合.

这是一个更具说明性的例子:

In[20]:= 
MatchQ[{-1,2,3,4,5},{_,x__?(Function[Print[#];Positive[#]])}]
During evaluation of In[20]:= 2
During evaluation of In[20]:= 3
During evaluation of In[20]:= 4
During evaluation of In[20]:= 5

Out[20]= True
Run Code Online (Sandbox Code Playgroud)

现在它打印所有这些,因为该函数逐个应用于它们,就像在这种情况下一样.

现在到了问题的关键.在这里,我设置了一个带副作用的测试函数,它决定在测试第一个元素后改变主意:

Module[{flag = False}, 
  ClearAll[test3]; 
  test3[x_] := 
     With[{fl = flag}, 
        If[! flag, flag = True]; 
        Print[x]; 
        fl
     ]
];
Run Code Online (Sandbox Code Playgroud)

第一个组合({-1},{2,3,4,5}由于函数False首先给出,它将被拒绝.但是第二个组合({-1,2},{3,4,5})将被接受.这正是我们观察到的:

In[22]:= 
MatchQ[{-1,2,3,4,5},{x__?test3,y__}]
During evaluation of In[22]:= -1
During evaluation of In[22]:= -1
During evaluation of In[22]:= 2

Out[22]= True
Run Code Online (Sandbox Code Playgroud)

一旦模式匹配器找到匹配,打印就会停止.

现在,从这里可以明显看出问题中提到的优化和其他一些答案通常是不可能的,因为模式匹配器无法控制测试函数中可能存在的可变状态.

在考虑模式匹配时,我们通常将其视为一个独立于评估的过程,这在很大程度上是正确的,因为模式匹配器是系统的内置组件,一旦模式和表达式评估,就会大量接管绕过主评估循环.然而,有一些值得注意的例外,它使得模式匹配更加强大,其代价是与评估者纠缠在一起.这些包括的用途ConditionPatternTest,因为这两个是主要的评估过程中的"进入点"到从它的模式匹配过程中的否则隔离.一旦模式匹配器击中其中一个,它就会在要测试的条件下调用主评估器,然后一切皆有可能.这让我再次看到模式匹配器在使用PatternTestCondition不存在的测试时最有效,并且模式完全是语法 - 在这种情况下,它可以优化.

  • @ Mr.Wizard我同意这不是最好的做法,但有时这可能是必要的.例如,有时您可能希望根据全局条件打开和关闭函数的某些定义,例如`f [x _]:= something /; someVariable`,其中`someVariable`是一个全局变量或表达式控制条件.我有几个案例,这是实现我想要的唯一简单方法.但由于你的问题是关于通用算法,而不是代码风格,答案似乎是这种可能性不能被排除,因此没有一般的优化. (2认同)
  • @ Mr.Wizard这会很好,但它会导致错误,因为它会给程序员的肩膀带来更多的责任,如果他/她使用不当,优化会以非常微妙的方式破坏他们的代码.即使有一个我给出的例子,*有一个优化不能重新计算`someVariable`,它给了我可怕的头痛,直到我回忆起我需要调用`Update [f]`.但是,我认为具有副作用的测试功能通常不能被视为极端情况.它们仅用于像我们这样的人通常关心的问题子集,它们是...... (2认同)
  • @ Mr.Wizard ...算法到很高的程度.很多真实案例都是临时性的,可能取决于我们无法控制的一些外部因素.您当然可以认为模式匹配对于那些可能是一个错误的范例,但由于这是最深的本地Mathematica结构,它通常会简化Mathematica中的事情.事实似乎是在中间的某个地方:我们通常应该避免以这种方式使用模式,但完全排除这些用途将是IMO过于极端 - 在现实世界中,可变性很重要. (2认同)