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

tel*_*f14 7 localization wolfram-mathematica function

使用消息Set提醒我们,可以轻松地跨两个列表进行多个分配,而不必分开任何内容.例如:

Remove[x1, x2, y1, y2, z1, z2];
{x1, x2} = {a, b}
Run Code Online (Sandbox Code Playgroud)

执行赋值并返回:

{a, b}
Run Code Online (Sandbox Code Playgroud)

Thread,通常用于生成规则列表,也可以显式调用以实现相同的结果:

Thread[{y1, y2} = {a, b}]
Thread[{z1, z2} -> {a, b}]
Run Code Online (Sandbox Code Playgroud)

得到:

{a, b}
{z1 -> a, z2 -> b}
Run Code Online (Sandbox Code Playgroud)

但是,使用此方法生成局部常量会产生错误.考虑这个简单的示例函数:

Remove[f];
f[x_] :=
 With[{{x1, x2} = {a, b}},
  x + x1 + x2
  ]
f[z]
Run Code Online (Sandbox Code Playgroud)

这里出现错误信息:

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."
Run Code Online (Sandbox Code Playgroud)

错误消息documentation(ref/message/With/lvw)在"更多信息"部分中说," 当With中的第一个元素不是符号赋值列表时,将生成此消息." 鉴于这种解释,我理解为什么我的任务失败的机制.尽管如此,我很困惑并且想知道这是否是WRI的必要限制,还是应该报告的次要设计监督.

所以这是我的问题:

任何人都可以对这种行为有所了解和/或提供解决方法吗?我试图强迫Evaluation,没有运气,我不知道还有什么可以尝试.

Leo*_*rin 11

你要求的是棘手的.这是宏的工作,已经被其他人公开了.我将探索一种不同的可能性 - 使用相同的符号,但在您要编写的代码周围放置一些包装器.这种技术的优点是代码在"词法"和"编译时"转换,而不是在运行时转换(如在其他答案中).这通常更快,更容易调试.

所以,这里有一个函数可以改变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]
Run Code Online (Sandbox Code Playgroud)

请注意,这适用于保留的代码.这样做的好处是,我们不必担心在开始时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]]
Run Code Online (Sandbox Code Playgroud)

然而,这使用起来不太方便.这是一个简化这个的便利功能:

ew = Function[code, ReleaseHold@expandWith@Hold@code, HoldAll]
Run Code Online (Sandbox Code Playgroud)

我们现在可以用它:

In[47]:= ew@With[{{x1,x2}={a,b}},x+x1+x2]
Out[47]= a+b+x
Run Code Online (Sandbox Code Playgroud)

因此,要在代码中进行扩展,只需将其ew环绕.以下是函数定义的情况:

Remove[f];
ew[f[x_] := With[{{x1, x2} = {a, b}}, x + x1 + x2]]
Run Code Online (Sandbox Code Playgroud)

我们现在检查并看到我们得到的是扩展定义:

?f
Global`f
f[x_]:=With[{x2=b,x1=a},x+x1+x2]
Run Code Online (Sandbox Code Playgroud)

这种方法的优点是可以包装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]
Run Code Online (Sandbox Code Playgroud)

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

  • @Simon鉴于Mathematica的元编程功能,我认为代码生成通常是一种方法.它既灵活又可组合,您不会以任何方式更改内置功能,并且您引入的更改是本地的(并且可能是词法范围).这很容易修改,易于调试.您可以根据需要修改"内置"-s的语法,因为mma评估者无论如何都不必评估该语法 - 它将首先由代码生成函数转换为标准函数 - 生成您编写的函数,并且仅然后评估. (2认同)

Rol*_*tig 7

棘手的问题是保持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]
Run Code Online (Sandbox Code Playgroud)

结果是

a+b+z
Run Code Online (Sandbox Code Playgroud)

受到下面的halirutan的启发,我认为他的解决方案稍微更加安全,与上述相同:

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]
Run Code Online (Sandbox Code Playgroud)


hal*_*tan 6

"LocalConstants"教程说

使用[{x =下标[x,0],...},body]的方法是获取body,并用Subscript [x,0]等替换它中的每一个x等等.你可以把它想象成/的概括.运算符,适用于Mathematica代码而不是其他表达式.

参考这个解释似乎很明显

x + x1 + x2 /. {x1, x2} -> {a, b}
Run Code Online (Sandbox Code Playgroud)

不会像在With符号中那样工作.

让我们假设你真的想破解这个.With[]具有HoldAll属性,因此不会评估您作为第一个参数提供的所有内容.要进行这样的矢量赋值工作,您必须创建

With[{x1=a, x2=b}, ...]
Run Code Online (Sandbox Code Playgroud)

来自矢量符号.不幸,

Thread[{a, b} = {1, 2}]
Run Code Online (Sandbox Code Playgroud)

不起作用,因为没有保持Thread的参数,并且在Thread可以执行任何操作之前评估赋值.

让我们解决这个问题

SetAttributes[myThread, HoldFirst];
myThread[Set[a_, b_]] := mySet @@@ Transpose[{a, b}]
Run Code Online (Sandbox Code Playgroud)

In[31]:= myThread[{a, b, c} = {1, 2, 3}]
Out[31]= {mySet[a, 1], mySet[b, 2], mySet[c, 3]}
Run Code Online (Sandbox Code Playgroud)

一开始看起来很有希望,只是把问题转移了一下.要使用此功能,With[]您必须在某些时候将mySet替换为真实的Set.恰好在那时,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]
Run Code Online (Sandbox Code Playgroud)

似乎并不容易解决这个问题,这里还有第二个问题:如果有一种方法可以绕过这种限制,它是否会像With那样快或者与模块相比失去速度优势?如果速度不是那么重要,为什么不首先使用Module或Block?

  • @ Mr.Wizard在模块和块中你至少可以做模块[{a,b,c},{a,b,c} = {1,2,3}; a + b + c]在With中不起作用. (2认同)