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
,但没有运气,我不知道还有什么可以尝试的
答案 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]