Предлагаемое решение
Кажется, что нет встроенной конструкции для автоматического тестирования вложенных головок. Мы можем достичь цели, написав функцию, которая для любого заданного (под) выражения формы 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]}
Дополнительные пояснения и комментарии
Как это работает
Вот некоторые подсказки для логики, которая приводит к этому решению, и как оно работает. Решение будет наиболее кратким и эффективным, если нам удастся использовать некоторые встроенные функции обхода выражений. На ум приходят Map
, Scan
, Cases
, MapIndexed
, Position
. Учитывая, что нам нужны головы, нам нужно передать опцию Heads->True
. Я использовал Scan
, так как этот легко остановить в любой точке (в отличие от других упомянутых конструкций, для которых вам обычно нужно бросить исключение, чтобы остановить их «в середине», что довольно неэлегично и вызывает некоторые накладные расходы а также) как только мы найдем то, что хотим. Наш результат будет самым первым, что Scan
найдет при обходе выражения в глубину, поэтому ожидается, что он будет очень эффективным (он не будет проходить по всему выражению).
Как избежать утечек оценки
Еще один комментарий к оценке. Вы можете видеть, что атрибут HoldAllComplete
используется в shead
, а 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
с таким поведением здесь , и эту функцию также можно успешно использовать вместо shead
в приведенном выше, хотя shead
более эффективен.