生成局部变量(作为常量)时出错

发布于 2024-12-19 05:07:39 字数 1039 浏览 2 评论 0原文

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) 在“更多信息”部分中表示,“此消息是生成的当 With 中的第一个元素不是符号赋值列表时。”有了这个解释,我就明白了我的作业失败的原因。尽管如此,我还是很困惑,想知道这是否是 WRI 的必要限制,或者是应该报告的轻微设计疏忽。

所以这是我的问题:

任何人都可以阐明这种行为并/或提供解决方法吗?我尝试过尝试强制评估,但没有运气好,我不知道还能尝试什么。

The usage message for Set reminds us that multiple assignments can easily be made across two lists, without having to rip anything apart. For example:

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

Performs the assignment and returns:

{a, b}

Thread, commonly used to generate lists of rules, can also be called explicitly to achieve the same outcome:

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

Gives:

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

However, employing this approach to generate localized constants generates an error. Consider this trivial example function:

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

Here the error message:

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."

The error message documentation (ref/message/With/lvw), says in the 'More Information' section that, "This message is generated when the first element in With is not a list of assignments to symbols." Given this explanation, I understand the mechanics of why my assignment failed. Nonetheless, I'm puzzled and wondering if this is necessary restriction by WRI, or a minor design oversight that should be reported.

So here's my question:

Can anyone shed some light on this behavior and/or offer a workaround? I experimented with trying to force Evaluation, without luck, and I'm not sure what else to try.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

逆流 2024-12-26 05:07:39

你的要求很棘手。正如其他人已经暴露的那样,这是宏的工作。我将探索一种不同的可能性 - 使用相同的符号,但在您想要编写的代码周围放置一些包装器。这种技术的优点是代码在“编译时”进行“词法”转换,而不是在运行时(如其他答案中所示)。这通常更快并且更容易调试。

因此,这里有一个函数可以用您建议的语法转换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 中创建这些代码。

编辑

在编写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]

我发现它比第一个要复杂得多。

What you request is tricky. This is a job for macros, as already exposed by the others. I will explore a different possibility - to use the same symbols but put some wrappers around the code you want to write. The advantage of this technique is that the code is transformed "lexically" and at "compile-time", rather than at run-time (as in the other answers). This is generally both faster and easier to debug.

So, here is a function which would transform the With with your proposed syntax:

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]

Note that this operates on held code. This has the advantage that we don't have to worry about possible evaluation o the code neither at the start nor when expandWith is finished. Here is how it works:

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]]

This is, however, not very convenient to use. Here is a convenience function to simplify this:

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

We can use it now as:

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

So, to make the expansion happen in the code, simply wrap ew around it. Here is your case for the function's definition:

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

We now check and see that what we get is an expanded definition:

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

The advantage of this approach is that you can wrap ew around an arbitrarily large chunk of your code. What happens is that first, expanded code is generated from it, as if you would write it yourself, and then that code gets executed. For the case of function's definitions, like f above, we cansay that the code generation happens at "compile-time", so you avoid any run-time overhead when usin the function later, which may be substantial if the function is called often.

Another advantage of this approach is its composability: you can come up with many syntax extensions, and for each of them write a function similar to ew. Then, provided that these custom code-transforming functions don't conlict with each other, you can simply compose (nest) them, to get a cumulative effect. In a sense, in this way you create a custom code generator which generates valid Mathematica code from some Mathematica expressions representing programs in your custom languuage, that you may create within Mathematica using these means.

EDIT

In writing expandWith, I used iterative rule application to avoid dealing with evaluation control, which can be a mess. However, for those interested, here is a version which does some explicit work with unevaluated pieces of code.

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]

I find it considerably more complicated than the first one though.

完美的未来在梦里 2024-12-26 05:07:39

棘手的问题是保持 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]

下面的 Inspired by halirutan的结果

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]

The tricky issue is to keep the first argument of Set unevaluated.
Here is my suggestion (open to improvements of course):

  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]

results in

a+b+z

Inspired by halirutan below I think his solution, made slightly more safely, is equivalent to the above:

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]
北方的巷 2024-12-26 05:07:39

教程“LocalConstants”说

With[{x=Subscript[x, 0],...},body] 的工作方式是获取 body,并替换每个
通过 Subscript[x, 0] 等在其中出现 x 等。您可以将 With 视为
/的概括。运算符,适合应用于 Mathematica 代码而不是
其他表达方式。

参考这个解释,显然类似的东西

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

不会像 With 表示法中预期的那样工作。

假设您确实想解决这个问题。 With[] 具有 HoldAll 属性,因此不会评估您作为第一个参数提供的所有内容。要进行这样的向量分配,您必须

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

根据向量符号进行创建。不幸的是,

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

这不起作用,因为没有保存 Thr​​ead 的参数,并且在 Thread 可以执行任何操作之前对赋值进行了评估。

让我们解决这个问题

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

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

一开始看起来很有希望,只是把问题移开了一点。要在 With[] 中使用它,您必须在某个时刻用真实的 Set 替换 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]

似乎没有解决这个问题的简单方法是,这里还有第二个问题:如果有办法解决这个限制,它是否和 With 一样快,或者我们是否会失去与 Module 相比的速度优势?如果速度不是那么重要,为什么不首先使用模块或块呢?

The tutorial "LocalConstants" says

The way With[{x=Subscript[x, 0],...},body] works is to take body, and replace every
occurrence of x, etc. in it by Subscript[x, 0], etc. You can think of With as a
generalization of the /. operator, suitable for application to Mathematica code instead of
other expressions.

Referring to this explanation it seems obvious that something like

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

will not work as it might be expected in the With notation.

Let's assume you really want to hack around this. With[] has the attribute HoldAll, therefore everything you give as first parameter is not evaluated. To make such a vector-assignment work you would have to create

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

from the vector-notation. Unfortunately,

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

does not work because the argument to Thread is not held and the assignment is evaluated before Thread can do anything.

Lets fix this

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

gives

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

What looks promising at first, just moved the problem a bit away. To use this in With[] you have to replace at some point the mySet with the real Set. Exactly then, With[] does not see the list {a=1, b=2, c=3} but, since it has to be evaluated, the result of all assignments

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]

There seems to be not easy way around this and there is a second question here: If there is a way around this restriction, is it as fast as With would be or do we lose the speed advantage compared to Module? And if speed is not so important, why not using Module or Block in the first place?

毅然前行 2024-12-26 05:07:39

您可以使用 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
   ]]

@Heike,是的,如果任一变量已经有值,则上述内容会中断。这个怎么样:

SetAttributes[myWith, HoldAll];
myWith[{Set[a_List, b_List]}, body_] := 
 ReleaseHold@
  Hold[With][Thread[Hold[a, b]] /. Hold[p__] :> Hold[Set[p]], 
   Hold@body]

You could use Transpose to shorten Rolfs solution by 100 characters:

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

@Heike, yep the above breaks if either variable has already a value. What about this:

SetAttributes[myWith, HoldAll];
myWith[{Set[a_List, b_List]}, body_] := 
 ReleaseHold@
  Hold[With][Thread[Hold[a, b]] /. Hold[p__] :> Hold[Set[p]], 
   Hold@body]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文