【问题标题】:Save Mathematica code in `FullForm` syntax以 `FullForm` 语法保存 Mathematica 代码
【发布时间】:2011-11-26 21:43:50
【问题描述】:

我需要在大型 Mathematica 代码库(数十万行代码)上进行一些元编程,并且不想编写成熟的解析器,所以我想知道如何最好地从以易于解析的语法输出的 Mathematica 笔记本。

是否可以以 FullForm 语法导出 Mathematica 笔记本,或以 FullForm 语法保存所有定义?

Savedocumentation 表示它只能以 InputForm 语法导出,这对解析很重要。

到目前为止,我最好的解决方案是评估笔记本,然后使用DownValues 提取带有参数的重写规则(但这错过了符号定义),如下所示:

DVs[_] := {}
DVs[s_Symbol] := DownValues[s]
stream = OpenWrite["FullForm.m"];
WriteString[stream, 
  DVs[Symbol[#]] & /@ Names["Global`*"] // Flatten // FullForm];
Close[stream];

到目前为止,我已经尝试了多种方法,但都没有奏效。 Mathematica 中的元编程似乎非常困难,因为它不断评估我不想评估的东西。例如,我想使用 SymbolName[Infinity] 获取无穷大符号的字符串名称,但 Infinity 被评估为非符号,并且对 SymbolName 的调用因错误而终止。因此,我希望用更合适的语言进行元编程。

编辑

最好的解决方案似乎是将笔记本手动保存为包 (.m) 文件,然后使用以下代码进行翻译:

stream = OpenWrite["EverythingFullForm.m"];
WriteString[stream, Import["Everything.m", "HeldExpressions"] // FullForm];
Close[stream];

【问题讨论】:

    标签: parsing syntax wolfram-mathematica metaprogramming


    【解决方案1】:

    您当然可以这样做。这是一种方法:

    exportCode[fname_String] := 
     Function[code, 
        Export[fname, ToString@HoldForm@FullForm@code, "String"], 
        HoldAllComplete]
    

    例如:

    fn = exportCode["C:\\Temp\\mmacode.m"];
    fn[
      Clear[getWordsIndices];
      getWordsIndices[sym_, words : {__String}] := 
          Developer`ToPackedArray[words /. sym["Direct"]];
    ];
    

    并将其作为字符串导入:

    In[623]:= Import["C:\\Temp\\mmacode.m","String"]//InputForm
    Out[623]//InputForm=
    "CompoundExpression[Clear[getWordsIndices], SetDelayed[getWordsIndices[Pattern[sym, Blank[]], \
    Pattern[words, List[BlankSequence[String]]]], Developer`ToPackedArray[ReplaceAll[words, \
    sym[\"Direct\"]]]], Null]"
    

    但是,考虑到 Mathematica 非常适合这种情况,使用其他语言为 Mathematica 进行元编程对我来说听起来很荒谬。 Mathematica 中有许多技术可用于进行元编程和avoid 过早评估。我在this 回答中描述了我想到的一个,但还有很多其他的。由于您可以对已解析的代码进行操作并使用 Mathematica 中的模式匹配,您可以节省很多。您可以浏览 SO Mathematica 标签(过去的问题)并找到许多元编程和评估控制的示例。

    编辑

    使用自动评估符号来减轻您的痛苦(实际上只有少数,Infinity 就是其中之一)。如果您只需要获取给定符号的符号名称,那么此功能将有所帮助:

    unevaluatedSymbolName =  Function[sym, SymbolName@Unevaluated@sym, HoldAllComplete]
    

    你把它当作

    In[638]:= unevaluatedSymbolName[Infinity]//InputForm
    Out[638]//InputForm="Infinity"
    

    或者,您可以通过SetAttributes 简单地将HoldFirst 属性添加到SymbolName 函数。一种方法是在全球范围内这样做:

    SetAttributes[SymbolName,HoldFirst]; SymbolName[Infinity]//输入形式

    然而,全局修改内置函数是危险的,因为它可能对 Mathematica 这样的大型系统产生不可预知的影响:

    ClearAttributes[SymbolName, HoldFirst];
    

    这是一个在本地使用的宏:

    ClearAll[withUnevaluatedSymbolName];
    SetAttributes[withUnevaluatedSymbolName, HoldFirst];
    withUnevaluatedSymbolName[code_] :=
      Internal`InheritedBlock[{SymbolName},
         SetAttributes[SymbolName, HoldFirst];
         code]
    

    现在,

    In[649]:= 
    withUnevaluatedSymbolName[
       {#,StringLength[#]}&[SymbolName[Infinity]]]//InputForm
    
    Out[649]//InputForm=  {"Infinity", 8}
    

    您可能还希望在一段代码中进行一些替换,例如,将给定符号替换为其名称。这是一个示例代码(我将其包装在Hold 中以防止其评估):

    c = Hold[Integrate[Exp[-x^2], {x, -Infinity, Infinity}]]
    

    在这种情况下进行替换的一般方法是使用保留属性(请参阅this 答案)和保留表达式中的替换(请参阅this 问题)。对于手头的情况:

    In[652]:= 
    withUnevaluatedSymbolName[
           c/.HoldPattern[Infinity]:>RuleCondition[SymbolName[Infinity],True]
    ]//InputForm
    
    Out[652]//InputForm=
    Hold[Integrate[Exp[-x^2], {x, -"Infinity", "Infinity"}]]
    

    ,虽然这不是唯一的方法。除了使用上述宏之外,我们还可以将对SymbolName 的修改编码到规则本身(这里我使用了一种更冗长的形式(Trott - Strzebonski 技巧)的就地评估,但您可以使用RuleCondition 作为好吧:

    ClearAll[replaceSymbolUnevaluatedRule];
    SetAttributes[replaceSymbolUnevaluatedRule, HoldFirst];
    replaceSymbolUnevaluatedRule[sym_Symbol] :=
      HoldPattern[sym] :> With[{eval = SymbolName@Unevaluated@sym}, eval /; True];
    

    现在,例如:

    In[629]:= 
    Hold[Integrate[Exp[-x^2],{x,-Infinity,Infinity}]]/.
          replaceSymbolUnevaluatedRule[Infinity]//InputForm
    Out[629]//InputForm=
        Hold[Integrate[Exp[-x^2], {x, -"Infinity", "Infinity"}]]
    

    实际上,整个答案很好地展示了各种元编程技术。根据我自己的经验,我可以将您引导至 thisthisthisthisthis 我的答案,其中元编程对于解决我正在解决的问题至关重要。您还可以通过 Mathematica 中所有函数都带有 Hold 属性的函数的比例来判断——如果记忆对我有用的话,这大约是 10-15%。所有这些函数实际上都是宏,对代码进行操作。对我来说,这是一个非常具有指示性的事实,它告诉我 Mathematica 非常依赖于它的元编程设施。

    【讨论】:

    • @Jon Harrop 事实上,我认为我在 Mathematica SO 标签上的帖子中有三分之一到一半使用一种或另一种(通常是几种)形式的元编程来实现他们的目标,所以在这里做很多其他人。虽然我同意无限评估模型和相当复杂的评估器使元编程变得更加困难,但它不仅完全有可能,而且非常常用。有关 Mathematica 元编程的一个非常简短的示例,请参阅这个最近的问题 stackoverflow.com/questions/8240943/… 及其所指的问题。
    • 啊,我的 MMA 试用版不支持 Export,所以我无法运行您的代码(我是 OOF)!使用您的代码,它似乎使用HoldAllCompleteHoldForm 来阻止对ToString 的函数参数和参数的评估,以便将给定的代码块转换为FullForm 语法中的字符串。这很好,但我如何将它应用到几个现有的笔记本上?
    • 我想解决的问题之一是编写 Mathematica 代码来为笔记本中的定义创建依赖关系图。你会怎么做?例如,给定a=3; f[x_]:=a+x,您将得到{f->a},然后可以通过PlotGraph 对其进行可视化。
    • @Jon Harrop 实际上我已经在变体(依赖项)中多次这样做了。您可以将其作为单独的问题发布,我将尝试挖掘代码。您可以从非常简单的开始,但是正确处理局部变量等是一项艰巨的任务。你可以在这里查看我的包:mathprogramming-intro.org/download/packages/...,它可以找到包间函数依赖关系。 David Wagner 在 Mathematica 杂志和他的书“Power programming with Mathematica: the kernel”中发表了他的依赖分析代码。我认为他的治疗是一个很好的起点。
    • 感谢您的参考。关于 Mathematica 的适用性,我认为这里的答案确实证明了 Mathematica 使这个简单的问题变得多么困难。我遇到了 Mathematica 内核无缘无故死机的问题,Mathematica 无法解析自己的文件的问题,当我不想要它时它评估表达式的问题等等。我将不得不解决比这更难的问题才能完成工作,而且我一直在努力使用 Mathematica 进行微不足道的元编程......
    【解决方案2】:

    可以从笔记本的CodeInput 单元格中提取完整形式的表达式,如下所示:

    $exprs =    
      Cases[
        Import["mynotebook.nb", "Notebook"]
      , Cell[content_, "Code"|"Input", ___] :>
          ToExpression[content, StandardForm, HoldComplete]
      , Infinity
      ] //
      Flatten[HoldComplete @@ #, 1, HoldComplete] & //
      FullForm
    

    $exprs 被分配了读取的表达式,包裹在 Hold 中以防止评估。然后可以将$exprs 保存到文本文件中:

    Export["myfile.txt", ToString[$exprs]]
    

    以这种方式阅读包文件 (.m) 会稍微容易一些:

    Import["mypackage.m", "HeldExpressions"] //
    Flatten[HoldComplete @@ #, 1, HoldComplete] &
    

    【讨论】:

    • 看起来很棒,但不起作用。首先,Cell 后面经常跟着其他东西,所以我不得不在"Code"|"Input" 之后添加一个额外的___,但现在我在输出中得到\[LeftSkeleton]707\[RightSkeleton],因为它已被截断并且有很多错误,包括Syntax::stresc: Unknown string escape \T. .有什么想法吗?
    • 至于Code[...] 模式,我忽略了单元格选项。哎呀。按照你的建议修复。至于骨架字符,似乎有些表达式是以Short 的形式编写的。我不确定为什么。如果将ToString 表达式更改为ToString[$exprs, StandardForm],会有帮助吗?
    • @WReach:就是这样,ToString 没有电话!我只是想评估第一个表达式(定义$exprs),甚至还没有到那一行!我不明白为什么任何东西都会被截断。在另一个笔记本上,我收到错误,包括ToExpression::esntx: "Could not parse \!\(BoxData[RowBox[{\"Find\", \"\[TripleDot]\", RowBox[{RowBox[{RowBox[<<1>>], \":=\", \" \", RowBox[<<1>>]}], \";\"}]}]]\) as Mathematica input."
    • 啊,除非这个截断的东西来自笔记本本身的输出单元。但是,即便如此,为什么 Mathematica 的解析器会死在 Mathematica 本身生成的语法上?!
    • 我可以用我的一些笔记本重现您的问题。我难住了。我已经把它带到了社区:Problems interpreting input cell box expressions
    猜你喜欢
    • 2019-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多