Ответ 1
Предлагаемое решение
Кажется, что нет встроенной конструкции для выборочного тестирования вложенных головок автоматически. Мы можем достичь цели, написав функцию, которая для любого данного (суб) выражения формы 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
более эффективным.