Ошибка генерации локализованных переменных (как констант) - PullRequest
7 голосов
/ 04 декабря 2011

Сообщение об использовании для Set напоминает нам, что можно легко выполнить несколько назначений в двух списках, не разбирая ничего.Например:

Remove[x1, x2, y1, y2, z1, z2];
{x1, x2} = {a, b}

Выполняет присвоение и возвращает:

{a, b}

Thread, обычно используемый для генерации списков правил, также может быть вызван явно для достижения того же результата:

Thread[{y1, y2} = {a, b}]
Thread[{z1, z2} -> {a, b}]

Дает:

{a, b}
{z1 -> a, z2 -> b}

Однако использование этого подхода для генерации локализованных констант приводит к ошибке.Рассмотрим этот тривиальный пример функции:

Remove[f];
f[x_] :=
 With[{{x1, x2} = {a, b}},
  x + x1 + x2
  ]
f[z]

Вот сообщение об ошибке:

With::lvset: "Local variable specification {{x1,x2}={a,b}} contains 
{x1,x2}={a,b}, which is an assignment to {x1,x2}; only assignments 
to symbols are allowed."

В документации к сообщению об ошибке (ref/message/With/lvw) говорится в разделе «Дополнительная информация», что « Это сообщение генерируется, когда первый элемент в С не является списком назначений для символов . "Учитывая это объяснение, я понимаю механизм, почему мое назначение не удалось.Тем не менее, я озадачен и задаюсь вопросом, является ли это необходимым ограничением WRI или небольшим надзором за дизайном, о котором следует сообщить.

Итак, вот мой вопрос:

Кто-нибудь может пролить некоторый свет на это поведение и / или предложить обходной путь? Я экспериментировал с попыткой форсировать Evaluation, без удачи, и я не уверен, что еще можно попробовать.

Ответы [ 4 ]

11 голосов
/ 04 декабря 2011

То, что вы просите, сложно. Это работа для макросов, как уже показали другие. Я исследую другую возможность - использовать одни и те же символы, но поместите несколько оберток вокруг кода, который вы хотите написать. Преимущество этого метода заключается в том, что код преобразуется «лексически» и во время компиляции, а не во время выполнения (как в других ответах). Как правило, это быстрее и проще для отладки.

Итак, вот функция, которая преобразует With с вашим предложенным синтаксисом:

Clear[expandWith];
expandWith[heldCode_Hold] :=
 Module[{with}, 
   heldCode /. With -> with //. {
       HoldPattern[with[{{} = {}, rest___}, body_]] :> 
              with[{rest}, body],
       HoldPattern[
         with[{
           Set[{var_Symbol, otherVars___Symbol}, {val_, otherVals___}], rest___}, 
           body_]] :>
              with[{{otherVars} = {otherVals}, var = val, rest}, body]
     } /. with -> With]

Обратите внимание, что это работает с удерживаемым кодом. Преимущество этого в том, что нам не нужно беспокоиться о возможной оценке кода ни в начале, ни в конце expandWith. Вот как это работает:

In[46]:= expandWith@Hold[With[{{x1,x2,x3}={a,b,c}},x+x1+x2+x3]]
Out[46]= Hold[With[{x3=c,x2=b,x1=a},x+x1+x2+x3]]

Это, однако, не очень удобно для использования. Вот удобная функция, чтобы упростить это:

ew = Function[code, ReleaseHold@expandWith@Hold@code, HoldAll]

Теперь мы можем использовать его как:

In[47]:= ew@With[{{x1,x2}={a,b}},x+x1+x2]
Out[47]= a+b+x

Итак, чтобы расширение произошло в коде, просто оберните ew вокруг него. Вот ваш случай для определения функции:

Remove[f];
ew[f[x_] := With[{{x1, x2} = {a, b}}, x + x1 + x2]]

Теперь мы проверим и увидим, что мы получили расширенное определение:

?f
Global`f
f[x_]:=With[{x2=b,x1=a},x+x1+x2]

Преимущество этого подхода заключается в том, что вы можете обернуть ew вокруг произвольно большого куска кода. Происходит следующее: сначала из него генерируется расширенный код, как если бы вы написали его самостоятельно, а затем этот код выполняется. В случае определений функций, таких как f выше, мы можем сказать, что генерация кода происходит во время компиляции, поэтому вы избегаете любых накладных расходов во время выполнения при последующем использовании функции, которые могут быть существенными, если функция вызывается часто.

Еще одним преимуществом этого подхода является его сочетаемость: вы можете придумать множество расширений синтаксиса, и для каждого из них написать функцию, аналогичную ew. Затем, при условии, что эти пользовательские функции преобразования кода не конфликтуют друг с другом, вы можете просто составить (вложить) их, чтобы получить кумулятивный эффект. В некотором смысле, таким образом, вы создаете собственный генератор кода, который генерирует действительный код Mathematica из некоторых выражений Mathematica, представляющих программы в вашем собственном языке, которые вы можете создавать в Mathematica, используя эти средства.

EDIT

При написании expandWith я использовал итеративное применение правил, чтобы избежать контроля оценки, что может привести к путанице. Тем не менее, для тех, кто заинтересован, вот версия, которая делает некоторую явную работу с неоцененными частями кода.

Clear[expandWithAlt];
expandWithAlt[heldCode_Hold] :=
 Module[{myHold},
    SetAttributes[myHold, HoldAll];
    heldCode //. HoldPattern[With[{Set[{vars__}, {vals__}]}, body_]] :>
     With[{eval = 
              (Thread[Unevaluated[Hold[vars] = Hold[vals]], Hold] /.
                   Hold[decl___] :> myHold[With[{decl}, body]])},
       eval /; True] //. myHold[x_] :> x]

Я считаю, что это намного сложнее, чем первый.

7 голосов
/ 04 декабря 2011

Сложная задача - сохранить первый аргумент Set без оценки. Вот мое предложение (конечно, открытое для улучшений):

  SetAttributes[myWith, HoldAll];
    myWith[{s : Set[a_List, b_List]}, body_] :=
     ReleaseHold@
      Hold[With][
       Table[Hold[Set][Extract[Hold[s], {1, 1, i}, Hold], 
         Extract[Hold[s], {1, 2, i}]], {i, Length@b}], Hold@body]
    x1 = 12;
    Remove[f];
    f[x_] := myWith[{{x1, x2} = {a, b}}, x + x1 + x2]
    f[z]

результаты в

a+b+z

Вдохновленный Халирутаном ниже, я думаю, что его решение, сделанное немного более безопасно, эквивалентно приведенному выше:

SetAttributes[myWith, HoldAll];
myWith[{Set[a : {__Symbol}, b_List]} /; Length[a] == Length[b], 
  body_] := 
 ReleaseHold@
  Hold[With][
   Replace[Thread[Hold[a, b]], Hold[x_, y_] :> Hold[Set[x, y]], 1], 
   Hold@body]
6 голосов
/ 04 декабря 2011

В учебнике "LocalConstants" написано

Способ работы [{x = Subscript [x, 0], ...}, body]] состоит в том, чтобы взять тело и заменить каждый появление x и т. д. в нем с помощью индекса [x, 0] и т. д. Вы можете думать о С как о обобщение /. оператор, подходящий для применения к коду Mathematica вместо другие выражения.

Ссылаясь на это объяснение, кажется очевидным, что что-то вроде

x + x1 + x2 /. {x1, x2} -> {a, b}

не будет работать так, как можно было бы ожидать в обозначении «С».

Предположим, вы действительно хотите взломать это. With[] имеет атрибут HoldAll, поэтому все, что вы указываете в качестве первого параметра, не оценивается. Чтобы сделать такое векторное назначение, вам нужно создать

With[{x1=a, x2=b}, ...]

из вектора-нотации. К сожалению,

Thread[{a, b} = {1, 2}]

не работает, потому что аргумент Thread не сохраняется, и присвоение оценивается до того, как Thread сможет что-либо сделать.

Давайте исправим это

SetAttributes[myThread, HoldFirst];
myThread[Set[a_, b_]] := mySet @@@ Transpose[{a, b}]

1024 * дает *

In[31]:= myThread[{a, b, c} = {1, 2, 3}]
Out[31]= {mySet[a, 1], mySet[b, 2], mySet[c, 3]}

Поначалу то, что выглядит многообещающе, просто немного отодвинуло проблему. Чтобы использовать это в With[], вы должны в какой-то момент заменить mySet на реальный набор. Именно тогда With[] не видит список {a = 1, b = 2, c = 3}, но, поскольку он должен быть оценен, результат всех назначений

In[32]:= With[
 Evaluate[myThread[{a, b, c} = {1, 2, 3}] /. mySet :> Set], a + b + c]

During evaluation of In[32]:= With::lvw: Local 
variable specification {1,2,3} contains 1, which is not an assignment to a symbol. >>

Out[32]= With[{1, 2, 3}, a + b + c]

Кажется, что это нелегкий путь, и здесь возникает второй вопрос: если есть способ обойти это ограничение, будет ли он таким же быстрым, как С, или мы потеряем преимущество в скорости по сравнению с модулем? И если скорость не так важна, почему бы не использовать модуль или блок?

3 голосов
/ 04 декабря 2011

Вы можете использовать Transpose, чтобы сократить решение Rolfs на 100 символов:

SetAttributes[myWith, HoldAll];
myWith[{Set[a_List, b_List]}, body_] := 
 ReleaseHold[Hold[With][Hold[Set[#1, #2]] & @@@ Transpose[{a, b}],
   Hold@body
   ]]

@ Эй, да, вышеприведенные разрывы, если любая из переменных уже имеет значение. Как насчет этого:

SetAttributes[myWith, HoldAll];
myWith[{Set[a_List, b_List]}, body_] := 
 ReleaseHold@
  Hold[With][Thread[Hold[a, b]] /. Hold[p__] :> Hold[Set[p]], 
   Hold@body]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...