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

时间:2011-12-04 05:34:11

标签: function localization wolfram-mathematica

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的必要限制,或者是否应该报告的次要设计监督。

所以这是我的问题:

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

4 个答案:

答案 0 :(得分: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]

请注意,这会对保留的代码进行操作。这样做的好处是,我们不必担心在开始时或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]

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

答案 1 :(得分: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]

结果

a+b+z

受到以下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]

答案 2 :(得分:6)

教程“LocalConstants”说

  

使用[{x =下标[x,0],...},body]的方式是采取身体,并取代每一个   通过下标[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}]

不起作用,因为没有保持Thread的参数,并且在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[]中使用此功能,您必须在某些时候将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]

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

答案 3 :(得分:3)

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