Арбитражное решение с глубоким вложенным шаблоном

Есть ли способ создать шаблон Mathematica, который соответствует выражениям, чьи головки могут быть сколь угодно глубокими, то есть что-то вроде f[___][___][___]...?

Ответы

Ответ 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 более эффективным.

Ответ 2

Как насчет следующего:

In[277]:= 
ArbitrarilyDeepHeadPattern[
  head_Symbol] := _?(Function[
    MemberQ[
      Position[#, _head, {0, Infinity}, Heads -> True], {0 ...}]])

In[281]:= Cases[{f[1], g[f[1]], f[1, 2, 3][1], f[1][2][3][4]}, 
 ArbitrarilyDeepHeadPattern[f]]

Out[281]= {f[1], f[1, 2, 3][1], f[1][2][3][4]}

Ответ 3

Здесь может быть использована стратегия рекурсивного сопоставления:

curried[head_] := _head | (x_[___] /; MatchQ[Hold[x], _[curried[head]]])

Использование:

In[26]:= $testCases = {f, f[1], g[f[1]], f[1,2,3][1], f[1][2][3][4]};
         Cases[$testCases, curried[f]]

Out[27]= {f[1],f[1,2,3][1],f[1][2][3][4]}

Обновление

В предположении Леонида Unevaluated можно использовать как более четкий и быстрый способ избежать оценки утечек в состоянии шаблона:

curried[head_] := _head | (x_[___] /; MatchQ[Unevaluated[x], curried[head]])

Ответ 4

Ответ WReach заставил меня пересмотреть рекурсивное определение, которое я пробовал вчера, но отказался.

Теперь я понимаю, что то, что я на самом деле работает, просто порождает ошибку. Это игрушка по сравнению с тонким методом Леонида, но у меня есть привязанность к кратким кодам, поэтому я размещаю ее здесь для интереса или развлечения. Перед запуском убедитесь, что у вас нет $RecursionLimit, установленного в Infinity.

Cases[
  {f, f[1], g[f[1]], f[1, 2, 3][1], f[1][2][3][4]}, 
  f // [email protected]#|#0[#]@_&
]

Или даже:

Cases[
  {f, f[1], g[f[1]], f[1, 2, 3][1], f[1][2][3][4]},
  p=_f|[email protected]_
]

Ответ 5

Вот альтернативная версия @Leonid shead, чтобы найти символическую главу выражения. (Вы должны использовать остальные его решение как есть.) Моя функция не предполагает рекурсии, но вместо этого она использует Level имеет особый случай, когда установка указателя levels на {-1} возвращает все атомные выражения, первая из которых - сама голова:

shead2[expr_] := [email protected][expr, {-1}, Heads -> True];

Он работает в соответствии с шаблоном так же, как shead делает:

In[264]:= Cases[{f[1], g[f[1]], f[1, 2, 3][1], f[1][2][3][4]}, 
 x_ /; shead2[x] === f]

Out[264]= {f[1], f[1, 2, 3][1], f[1][2][3][4]}

И чтобы понять, как это работает, вот как Level ведет себя с параметрами levelspec, установленными на {-1}:

In[263]:= 
Level[#, {-1}, Heads -> True] & /@ {f[1], g[f[1]], f[1, 2, 3][1], 
  f[1][2][3][4]}

Out[263]= {{f, 1}, {g, f, 1}, {f, 1, 2, 3, 1}, {f, 1, 2, 3, 4}}