建议的解决方案
似乎没有内置结构可以自动对嵌套头进行模式测试。我们可以通过编写一个函数来实现这个目标,该函数对于f[___]...[___] 形式的任何给定(子)表达式,有效地确定f(在稍微滥用术语的情况下,我们可以将表达式称为符号头)。代码如下:
ClearAll[shead];
SetAttributes[shead, HoldAllComplete];
shead[expr_] := Scan[Return, Unevaluated[expr], {-1}, Heads -> True];
下面是它的使用方法(我将使用与@Sasha 相同的一组测试):
In[105]:= Cases[{f[1], g[f[1]], f[1, 2, 3][1], f[1][2][3][4]}, x_ /; shead[x] === f]
Out[105]= {f[1], f[1, 2, 3][1], f[1][2][3][4]}
模式语法
如果您更喜欢使用@Sasha 建议的语法,则该版本看起来像
Clear[headPattern];
headPattern[head_] := _?(Function[Null, shead[#] === head, HoldFirst]);
In[108]:= Cases[{f[1], g[f[1]], f[1, 2, 3][1], f[1][2][3][4]}, headPattern[f]]
Out[108]= {f[1], f[1, 2, 3][1], f[1][2][3][4]}
进一步解释和cmets
工作原理
以下是导致此解决方案的逻辑及其工作原理的一些提示。如果我们设法利用一些内置的表达式遍历函数,该解决方案将是最简洁和高效的。想到的一些是Map、Scan、Cases、MapIndexed、Position。鉴于我们需要磁头,我们需要传递Heads->True 选项。我使用了Scan,因为这个很容易在任何时候停止(与其他提到的构造不同,你通常需要抛出一个异常来阻止它们“在中间”,这是相当不雅的并且会导致一些开销以及)一旦我们找到我们想要的。我们的结果将是Scan 在其深度优先表达式遍历中发现的第一件事,因此预计它会非常有效(它不会遍历整个表达式)。
避免评估泄漏
另一条评论是关于评估的。可以看到shead中使用了HoldAllComplete属性,其body中使用了Unevaluated。这些非常重要 - 它们用于防止对传递给函数的表达式进行可能的评估。在这种情况下可能很重要:
In[110]:= m = n = 0;
g[x_] := n++;
h[x_] := m++;
{Cases[Hold[f[g[1]][h[2]]], x_ /; shead[x] === f :> Hold[x], Infinity], {m, n}}
Out[113]= {{Hold[f[g[1]][h[2]]]}, {0, 0}}
在这里,我们看到了我们所期望的——尽管Cases 一直在遍历整个表达式并将其(子)部分提供给shead,但shead 并未触发对子部分的评估。现在我们定义一个“泄漏评估”的shead 的幼稚版本:
sheadEval[expr_] := Scan[Return, expr, {-1}, Heads -> True]
现在,
In[114]:= {Cases[Hold[f[g[1]][h[2]]], x_ /; sheadEval[x] === f :> Hold[x], Infinity], {m, n}}
Out[114]= {{Hold[f[g[1]][h[2]]]}, {2, 1}}
后一种行为通常不能令人满意。整个代码即数据范式在元编程中非常有用,在 Mathematica 中非常强大,因为您可以使用规则来解构代码。模式匹配期间可能的(不需要的)评估会极大地损害它。整个问题在于子部分。包装 Hold 只会阻止整个表达式的求值。 Cases 之类的函数和其他类似的代码解构函数非常棒,因为它们在进行结构(句法)匹配时不会评估子部分。
对符号头的评论
这里的最后一条评论(主要是关于定义)是 shead 函数返回的并不完全是 Mathematica 中通常称为的 符号头。区别在于原子表达式。例如,shead[f] 返回f,而对于原子表达式,真正的符号头应该与表达式的头重合(在这种情况下为Symbol)。我开发了symbolicHead 函数,具有here 的这种行为,也可以成功地使用它来代替上面的shead,尽管shead 更有效。