Mathematica 何时创建新符号?

5

您好,

之前我认为Mathematica在将输入字符串(分配给InString)转换为输入表达式(分配给In)的阶段会在当前$Context中创建新符号。但一个简单的例子打破了这个解释:

In[1]:= ?f
During evaluation of In[1]:= Information::notfound: Symbol f not found. >>
In[2]:= Names["`*"]
Out[2]= {}
In[3]:= DownValues[In]//First
InString[1]
Names["`*"]
Out[3]= HoldPattern[In[1]]:>Information[f,LongForm->False]
Out[4]= \(? f\)
Out[5]= {}

你可以看到,在In[1]的定义中已经使用了符号f,但在$ContextPath中没有它的符号。

这个例子表明,在Mathematica中原则上可以使用不存在于$ContextPath中的符号进行定义,而不必创建它们。这可能是避免使用Symbol方法创建符号的有趣替代方法:

In[9]:= ff := Symbol["f"]
Names["`*"]
Out[10]= {"ff"}

有人能解释一下在哪些条件和评估过程的哪个阶段,Mathematica会创建新符号吗?

编辑

正如Sasha在对这个问题的评论中注意到的那样,在默认样式表Core.nb中,输出单元格的默认设置为ShowStringCharacters->False,我被愚弄了,并错过了DownValues[In]//First的输出的FullForm。实际上,我们可以通过使用InputForm来看到,在In[1]的定义中没有使用符号f

In[1]:= ?f
DownValues[In]//First//InputForm
During evaluation of In[1]:= Information::notfound: Symbol f not found. >>
Out[2]//InputForm=
HoldPattern[In[1]] :> Information["f", LongForm -> False]

抱歉之前的陈述过于仓促。
现在问题只是关于Mathematica何时决定创建新的Symbol以及我们如何防止它?例如,在上面的例子中,我们将f输入为Symbol,但Mathematica将其转换为String而不创建新的符号。这是MakeExpression的内置行为。
In[1]:= ?f
InputForm[MakeExpression[ToExpression@InString[1], StandardForm]]

During evaluation of In[1]:= Information::notfound: Symbol f not found. >>

Out[2]//InputForm=
HoldComplete[Information["f", LongForm -> False]]

可能可以定义某种语法结构,以防止在评估时创建符号。

关于创建新符号的评估阶段

我们可以看到,在调用MakeExpression之前会增加$Line,但是在调用MakeExpression后才会创建新的Symbol并分配新值给InStringIn变量:

In[1]:= MakeExpression[My`boxes_,My`f_]/;!TrueQ[My`$InsideMakeExpression]:=Block[{My`$InsideMakeExpression=True},Print[$Line];Print[DownValues[InString][[All,1]]];Print[DownValues[In][[All,1]]];Print[Names["`*"]];MakeExpression[My`boxes,My`f]];
In[2]:= a
During evaluation of In[2]:= 2
During evaluation of In[2]:= {HoldPattern[InString[1]]}
During evaluation of In[2]:= {HoldPattern[In[1]]}
During evaluation of In[2]:= {}
Out[2]= a

同样的,我们也可以这样说 $PreRead$NewSymbol 的调用时间:
In[1]:= $NewSymbol:=Print["Names[\"`*\"]=",Names["`*"],"\nDownValues[InString]=",DownValues[InString][[All,1]],"\nDownValues[In]=",DownValues[In][[All,1]],"\nName: ",#1,"\tContext: ",#2]&
In[2]:= a
During evaluation of In[2]:= Names["`*"]={}
DownValues[InString]={HoldPattern[InString[1]]}
DownValues[In]={HoldPattern[In[1]]}
Name: a Context: Global`
Out[2]= a

$Pre 在新的赋值给 In 变量之后执行,且在当前 $Context 中创建所有新的 Symbol 之后执行:

In[1]:= $Pre := (Print[Names["`*"]]; 
   Print[DownValues[In][[All, 1]]]; ##) &

In[2]:= a

During evaluation of In[2]:= {a}

During evaluation of In[2]:= {HoldPattern[In[1]],HoldPattern[In[2]]}

Out[2]= a

似乎无法拦截为In变量赋新值
结论:在调用$PreReadMakeExpression$NewSymbol之后,但在调用$Pre之前创建了新的Symbol

执行 DownValues[In] // First // FullForm 后,我看到 RuleDelayed[HoldPattern[In[1]],Information["f",Rule[LongForm,False]]] - Sasha
@ Sasha,你说得对,我错过了FullForm。这意味着在定义In时,实际上没有使用符号f,这与我之前的想法不同。但你能否评论一下我对符号创建的理解(在问题的第一段中描述)是否正确? - Alexey Popkov
3个回答

6

关于您在编辑部分提出的问题:不确定这是否符合您的意图,但在FrontEnd会话中,您可以使用$PreRead在解析阶段保留符号作为字符串。以下是一个可能的方法:

symbolQ = StringMatchQ[#, RegularExpression["[a-zA-Z$][a-zA-Z$`0-9]*"]] &;

ClearAll[keepSymbolsAsStrings];
SetAttributes[keepSymbolsAsStrings, HoldAllComplete];

$PreRead  = # //. RowBox[{"keepSymbolsAsStrings", rest___}] :>
 RowBox[{"keepSymbolsAsStrings", 
   Sequence @@ ({rest} //. x_String?symbolQ :>
       With[{context = Quiet[Context[x]]},            
        StringJoin["\"", x, "\""] /; 
         Head[context] === Context])}] &;

如果符号尚不存在(通过Context[symbol_string_name]检查),则将其转换为字符串。例如。
In[4]:= keepSymbolsAsStrings[a+b*Sin[c]]//FullForm

Out[4]//FullForm= keepSymbolsAsStrings[Plus["a",Times["b",Sin["c"]]]]

很重要的是首先定义keepSymbolsAsStrings,以便创建该符号。这使得它可重入:

In[6]:= keepSymbolsAsStrings[a+b*Sin[c]*keepSymbolsAsStrings[d+e*Sin[f]]]//FullForm

Out[6]//FullForm= 
  keepSymbolsAsStrings[Plus["a",Times["b",Sin["c"],
  keepSymbolsAsStrings[Plus["d",Times["e",Sin["f"]]]]]]]

现在,在您的代码被解析后,您可以以您喜欢的方式处理这些符号(保留为字符串)。您也可以使用不同的symbolQ函数 - 我只是为了举例而使用了一个简单的函数。但这对于包来说是行不通的。我没有看到一种直接的方法来做到这一点。一个简单的方法是动态重新定义Needs,以类似的方式在字符串级别上修改源代码,并有效地调用修改后的源代码的Needs。但是,字符串级别的源代码修改通常是脆弱的。希望对您有所帮助。 编辑 上面的代码有一个缺陷,就是很难区分哪些字符串是要转换为符号的,哪些是本来就是字符串的。您可以通过将ClearAll[keepSymbolsAsStrings]更改为ClearAll[keepSymbolsAsStrings, symbol],并将StringJoin["\"", x, "\""]更改为RowBox[{"symbol", "[", StringJoin["\"", x, "\""], "]"}]来修改上面的代码,以跟踪结果表达式中哪些字符串对应于转换后的符号。 编辑2

这是修改后的代码,基于MakeExpression而不是$PreRead,正如@Alexey所建议的:

symbolQ =  StringMatchQ[#, RegularExpression["[a-zA-Z$][a-zA-Z$0-9`]*"]] &;

ClearAll[keepSymbolsAsStrings, symbol];
SetAttributes[keepSymbolsAsStrings, HoldAllComplete];

Module[{tried},
 MakeExpression[RowBox[{"keepSymbolsAsStrings", rest___}], form_] :=
  Block[{tried = True},
    MakeExpression[
       RowBox[{"keepSymbolsAsStrings", 
         Sequence @@ ({rest} //. x_String?symbolQ :>
            With[{context = Quiet[Context[x]]},            
             RowBox[{"symbol", "[", StringJoin["\"", x, "\""], "]"}] /;
             Head[context] === Context])}], form]
  ] /;!TrueQ[tried]
]

我们需要 Todd Gayley 的 技巧 来打破在 MakeExpression 定义中的无限递归。这里再次给出示例:

In[7]:= keepSymbolsAsStrings[a+b*Sin[c]]//FullForm

Out[7]//FullForm= keepSymbolsAsStrings[Plus[symbol["a"],Times[symbol["b"],Sin[symbol["c"]]]]]

In[8]:= keepSymbolsAsStrings[a+b*Sin[c]*keepSymbolsAsStrings[d+e*Sin[f]]]//FullForm

Out[8]//FullForm=  keepSymbolsAsStrings[Plus[symbol["a"],Times[symbol["b"],Sin[symbol["c"]],
keepSymbolsAsStrings[Plus[symbol["d"],Times[symbol["e"],Sin[symbol["f"]]]]]]]]

这种方法更加简洁,因为$PreRead仍然对最终用户可用。


谢谢您提供这个聪明的解决方案!但是,也许通过为MakeExpression创建一个新定义的方法可以在保留$PreRead自由性的同时产生相同的开销。 - Alexey Popkov
确实,如果您能够使其正常工作(我没有尝试过),使用MakeExpression听起来像是一种更干净的替代方案,特别是如果您不是最终用户。 - Leonid Shifrin
从Simon对我的最近的答案的编辑中,我发现使用MakeExpression出现问题的主要原因是它必须返回一个Hold*表达式。 - Alexey Popkov
1
@Alexey 谢谢你提供的链接,非常有趣。我一直以为 MakeExpression 通常会用 HoldComplete 包装结果而不是 Hold。无论如何,我已经相应地更新了我的代码。 - Leonid Shifrin

4
你可以使用 $NewSymbol$NewMessage 来更好地理解符号何时被创建。但是从虚拟书中可以看出,当符号既不在 $Context 中,也不在 $ContextPath 中时,它会在 $Context 中被创建。

2
我认为您的基本理解是正确的,即当输入被解析成表达式时会创建符号。
微妙的部分在于行首的问号(以及双尖括号)会特别解析,允许使用字符串而不需要引号。这里的隐式字符串是模式,例如对于问号而言是"*Min*",对于双尖括号而言则是文件名。请注意保留HTML标签。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接