【问题标题】:How to Block Symbols without evaluating them?如何在不评估符号的情况下阻止符号?
【发布时间】:2011-06-04 04:34:03
【问题描述】:

假设我有一个Symbols 的名称列表:

f1 := Print["f1 is evaluated!"];
list = {"f1", "f2"};

Block 这些Symbols 的明显方法导致对它们进行评估:

In[19]:= With[{list=Symbol/@list},Block[list,f1//ToString]]
During evaluation of In[19]:= f1 is evaluated!
During evaluation of In[19]:= f1 is evaluated!
Out[19]= Null

但如果没有评估,我们可以Block他们没有任何问题:

In[20]:= Block[{f1, f2}, f1 // ToString]
Out[20]= "f1"

是否可以将此列表注入Block 范围而不评估Symbols?

【问题讨论】:

    标签: wolfram-mathematica


    【解决方案1】:

    这里还有另一种技术可以做到这一点:

    SetAttributes[blockAlt,HoldRest];
    blockAlt[s : {__String}, body_] :=
       Replace[Join @@ ToHeldExpression[s], Hold[x__] :> Block[{x}, body]]
    

    由于规则的破坏性,我们在这里保存纯函数(它们不尊重其他范围构造,包括它们自己)

    编辑

    另一种选择(甚至更短):

    SetAttributes[blockAlt1, HoldRest];
    blockAlt1[s : {__String}, body_] :=
       Block @@ Append[ToHeldExpression@ToString[s], Unevaluated[body]] 
    

    【讨论】:

    • +1 可以将ToHeldExpression[s](V3 中已弃用)重新表达为ToExpression[s, InputForm, Hold]
    • @WReach 当然,我很清楚这一点(这是我自己通常做的)——只是想让代码更短:)。我对我不太好,因为ToHeldExpression 确实已被弃用。
    • 不错! ToHeldExpression 说“自 3.0 版(1996 年发布)以来,ToHeldExpression 已被 ToExpression 取代”这一事实是否存在任何风险?好吧,看起来 WReach 打败了我,但是,这有什么风险吗?它会在未来的版本中消失吗?
    • @Alexey 这个问题实际上更普遍。以Map 为例。我很确定,如果您在全局范围内设置Heads->True,将会破坏多个功能。解决方案很简单:显式传递选项,但可惜的是,实际上几乎没有人这样做。如此广泛使用的快捷方式@@@@@ 甚至不允许这样做。而且我同意开发人员必须比用户小心得多。
    • @Alexey 这不是系统缺陷——这只是一类错误,来自粗心(至少这是我对它的看法)。这个问题的实际解决方案非常简单。如果您开发一个包,请定义一个私有函数map[f_,x_,levspec_:1]:=Map[f,x,levspec,Heads->False] 并始终使用map 代替Map,其他函数也是如此。但这需要更多的纪律。在 Mathematica 的上下文中,我很确定可以轻松开发类似于用于 C 的 lint 和用于 Java 的 FindBugs 的自动工具来检测此类潜在的错误。
    【解决方案2】:

    免责声明:虽然我的回复提供了解决问题的方法,但我不建议将其用于常规使用。我提供它是因为它可能具有一些学术兴趣。

    有时,通常在调试上下文中,我渴望地查看 Lisp 的 MACROEXPAND-1,并希望有一个 Mathematica 函数,它只对其参数应用一个级别的评估。让我们将这个神秘的函数称为EvaluateOnce。它会找到适用于表达式的转换规则并仅应用该规则,如下所示:

    In[19]:= fact[0] = 1; fact[x_] := x * fact[x - 1]
             EvaluateOnce[fact[5]]
    Out[19]= Hold[5 fact[5-1]]
    
    In[20]:= f1 := Print["f1 is evaluated!"];
             EvaluateOnce[Symbol["f1"]]
    Out[20]= Hold[f1]
    

    它也适用于多个表达式:

    In[21]:= EvaluateOnce[1 + 2 * 3, Sqrt @ Sin @ Pi]
    Out[22]= Hold[1+6, Sqrt[0]]
    

    当前的问题可以从这种能力中受益,那么解决方案可以表示为:

    EvaluateOnce @@ Symbol /@ Hold @@ list /.
      Hold[args__] :> Block[{args}, f1 // ToString]
    

    唉,编写这样一个函数存在许多技术障碍——尤其是在 Mathematica 中究竟什么是“单级评估”存在一定程度的模糊性。但是傻瓜会冲进天使不敢踏足的地方,所以我提供了这个hack

    ClearAll@EvaluateOnce
    SetAttributes[EvaluateOnce, HoldAllComplete]
    EvaluateOnce[exprs:PatternSequence[_, __]] :=
      Replace[Hold @@ Evaluate /@ EvaluateOnce /@ Hold[exprs], Hold[e_] :> e, 1]
    EvaluateOnce[expr_] :=
      Module[{depth = 0, length = 1+Length@Unevaluated@expr, tag, enter, exit}
      , SetAttributes[exit, HoldAllComplete]
      ; enter[in_]:= If[1 === depth && 0 === length, Throw[in, tag], ++depth]
      ; exit[in_, out_] := (If[2 === depth, length--]; depth--)
      ; Hold @@ Catch[With[{r = TraceScan[enter, expr, _, exit]}, Hold[r]], tag]
      ]
    

    此功能没有保修 :) 它使用TraceScan 和一些启发式方法来猜测“单级评估”何时完成,然后使用ThrowCatch 提前终止评估序列。

    对于“第一级评估”保持在标准评估范围内的函数定义,启发式方法似乎可以令人满意地工作。对于那些不这样做的人来说,它也惨遭失败。我也确信它会与某些评估属性的应用混淆。

    尽管存在这些错误,但在尝试调试甚至只是理解具有大量标准模式匹配定义的函数时,我仍然觉得这个函数很方便。

    【讨论】:

    • +1 - 非常有趣的功能,将尝试使用它。我一直认为可以使用Trace- 家庭函数来完成类似的事情,但从来没有想出有用的东西。 FWIW,我(并且现在)也对一步评估感兴趣,但通过构建自定义评估器以不同的方式解决了这个问题。可能是,您可能会在此线程中找到我的帖子,这有一些兴趣:groups.google.com/group/comp.soft-sys.math.mathematica/…
    • @Leonid 感谢您提供有趣的链接。我也曾尝试在 Mma 代码中重现标准评估器。正是那些讨厌的内置定义使事情变得困难。我希望看到 WRI 实施某种形式的部分评估,尽管我承认该功能在我的 Mma 愿望清单上并不是特别高。
    【解决方案3】:

    你可以尝试使用ToExpression:

    In[9]:= list = {"f1", "f2"};
    
    In[19]:= f1 = 25;
    
    In[20]:= ToExpression[
     StringJoin["{", Riffle[list, ","], "}"], InputForm, 
     Function[vars, Block[vars, f1], HoldAll]]
    
    Out[20]= 25
    

    【讨论】:

    • +1 内部的Block 表达式应该是Block[vars, f1 // ToString] -- 否则如果f1 的定义与原始问题中的一样,则会发生不需要的评估。
    • @WReach 我认为这里的f1Block 的主体,所以它的评估是有意的。关键是它只评估一次,因为它应该。一个更好的例子是Block[vars,Print[f1]],这确实会打印出f1
    • @Leonid 使用f1 的原始定义,编写的示例打印出f1 is evaluated!,OP 试图通过Block 避免这种情况。 Sasha 的解决方案是正确的,但一开始我很困惑,直到我意识到打印输出是在完全评估 ToExpression 表单之后生成的。我建议ToString 与原始问题相匹配,因为其他人可能同样感到困惑(但也许我是唯一的一个)。
    • @WReach 哦,我明白了。我认为使用f1 作为Block 的主体可能确实不是最好的选择。
    【解决方案4】:

    你可以考虑这个结构:

    SetAttributes[block, HoldRest]
    
    block[s : {__String}, body_] := 
     Function[, Block[{##}, body], HoldAll] @@ 
      Join @@ MakeExpression /@ s
    

    第二次尝试简化 Leonid 的第二个功能:

    block =
      Function[, Block @@ ToHeldExpression@ToString@#~Join~Hold@#2, HoldRest]
    

    【讨论】:

    • 最好为block 设置HoldRest 属性而不是HoldAll。 +1
    • @Mr.Wizard 如何使用新的block?我试过block[list, f1 // ToString],但它不适用于新版本。
    • @Alexey,看来我把它弄坏了。 ;-) 或不?这行得通吗? list = {"f1", "f2"}; f1 := Print["Evaluated!"]; block[list, f1 // ToString] 不管怎样,这只是为了好玩。
    • @Mr.Wizard 它有效。看来我只是在使用新定义之前没有重新启动内核。不错的解决方案!
    • 仔细观察,您的简洁版本的工作方式有所不同,并且包含我试图避免的功能 - 即将Block 的正文转换为字符串。转换为字符串并不总是无辜的,例如,这里block[{}, Hold[Print["a", "b"]]],我们将ab 作为符号返回,所以我们在ToString-ToExpression 循环中失去了它们的字符串性质。事实上,当我做ToString[s] 时,我正在(建设性地)使用这种字符串引号的丢失,但这是安全的,因为s 是一个字符串列表。所以,很抱歉把它带给你,但你的版本并不总是能正常工作。
    猜你喜欢
    • 1970-01-01
    • 2021-08-06
    • 2012-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-14
    相关资源
    最近更新 更多