Mathematica符号和语法修改

6

我正在使用Notation包在Mathematica中尝试语法修改。

我对特定领域的数学符号不感兴趣,而是对通用的语法修改和扩展感兴趣,尤其是可以减少Mathematica的VeryLongFunctionNames的冗长性、清理笨重的结构或以令人愉悦的方式扩展语言的符号。

一个示例修改是将Fold[f, x]定义为Fold[f, First@x, Rest@x]进行求值。
这个修改效果很好,非常方便。

另一个示例修改是将*{1,2}定义为Sequence @@ {1,2},灵感来自于Python;这在Mathematica中可能行也可能不行。

请提供有关以下内容的信息或链接:

  • 符号和语法修改的限制

  • 实现的技巧和诀窍

  • 现有的包、示例或实验

  • 为什么这是一个好或坏的想法

3个回答

5

这并不是一个有建设性的回答,只是一些想法。首先,免责声明 - 我不建议以下任何方法作为良好的实践(可能通常它们不是),它们只是一些似乎可以解决您特定问题的可能性。关于所述目标 - 我非常支持这个想法,能够减少冗余很棒(至少对于独立开发者的个人需求)。至于工具:我对符号包的经验非常少,但无论是否使用它或编写一些自定义的框操作预处理器,我的感觉是整个事实,即输入表达式必须由Mathematica解析器解析为框严重限制了可以做的事情。此外,将在包中使用它可能会遇到困难,正如其他回复中已经提到的。

如果有像$PreRead这样的钩子,最简单的方法就是允许用户拦截输入字符串并将其处理成另一个字符串,然后再将其馈送给解析器。那将允许编写一个自定义的预处理器,它在字符串级别上运行 - 或者您可以称之为编译器,从您设计的任何语法字符串生成Mathematica代码。我不知道这样的挂钩(当然可能是我的无知)。如果缺少这个,可以使用例如program样式的单元格,并且可能编写一些按钮,从这些单元格中读取字符串,并调用这样的预处理器来生成Mathematica代码并将其粘贴到原始代码所在的单元格旁边。

如果您想要的语言是一种简单的语言(至少在其语法和语法上),那么这种预处理器方法将最有效,以便易于词法分析和解析。如果您想要Mathematica语言(除了您想要更改的几个元素之外的全部语法),在这种方法中,您将没有运气,因为无论您的更改有多少和“轻量级”,您都需要重新实现基本上完全的Mathematica解析器,只是为了使这些更改可靠地工作。换句话说,我想说的是,在我看来,编写一个预处理器,它将从类似Lisp的语言生成Mathematica代码,语法很少或没有,比尝试对标准mma进行一些语法修改要容易得多。

从技术上讲,编写这样的预处理器的一种方法是使用像Lex(Flex)和Yacc(Bison)之类的标准工具来定义您的语法并生成解析器(例如在C中)。这样的解析器可以通过MathLink或LibraryLink(在C的情况下)插入回Mathematica。它的最终结果将是一个字符串,当解析时,它将成为有效的Mathematica表达式。该表达式将表示您解析的代码的抽象语法树。例如,像这样的代码(此处介绍了Fold的新语法)

"((1|+|{2,3,4,5}))"

可以解释为类似于

"functionCall[fold,{plus,1,{2,3,4,5}}]"

这样的预处理器的第二个组件将使用Mathematica编写,可能以基于规则的方式,从AST生成Mathematica代码。生成的代码必须以某种方式保持未评估状态。对于上面的代码,结果可能如下:

Hold[Fold[Plus,1,{2,3,4,5}]]

最好能够在Mathematica中使用类似Lex(Flex)/Yacc(Bison)这样的工具,即绑定(Bindings),只需在Mathematica中编写代码,就可以自动生成C解析器,并通过MathLink或LibraryLink插入到内核中。我希望它们将在未来版本中提供。如果没有这个工具,所述方法需要大量的低级工作(C或Java)。然而,我认为它仍然可行。如果您会写C(或Java),可以尝试编写一些相对简单(从语法/语法角度来看)的语言 - 这可能是一个有趣的项目,并且可以了解更复杂语言的情况。我会从一个非常基本的计算器示例开始,然后将标准算术运算符更改为一些Mathematica无法正确解析的更奇怪的运算符,以使其更有趣。为了避免MathLink/LibraryLink的复杂性并进行测试,您可以使用Run从Mathematica调用生成的可执行文件,并将代码作为命令行参数之一传递,然后将结果写入临时文件,再将其导入到Mathematica中。对于计算器示例,整个过程可以在几小时内完成。
当然,如果您只想缩写某些长函数名称,有一个更简单的替代方案 - 您可以使用With。以下是一个实际示例,即我移植的Peter Norvig的拼写校正器,在其中我使用了这种方法来减少行数:
Clear[makeCorrector];
makeCorrector[corrector_Symbol, trainingText_String] :=
Module[{model, listOr, keys, words, edits1, train, max, known, knownEdits2},
(* Proxies for some commands - just to play with syntax a bit*)
With[{fn = Function, join = StringJoin, lower = ToLowerCase, 
 rev = Reverse, smatches = StringCases, seq = Sequence, chars = Characters, 
 inter = Intersection, dv = DownValues, len = Length, ins = Insert,
 flat = Flatten, clr = Clear, rep = ReplacePart, hp = HoldPattern},
(* body *)
listOr = fn[Null, Scan[If[# =!= {}, Return[#]] &, Hold[##]], HoldAll];
keys[hash_] := keys[hash] = Union[Most[dv[hash][[All, 1, 1, 1]]]];
words[text_] := lower[smatches[text, LetterCharacter ..]];
With[{m = model}, 
 train[feats_] := (clr[m]; m[_] = 1; m[#]++ & /@ feats; m)];
 With[{nwords = train[words[trainingText]], 
  alphabet = CharacterRange["a", "z"]},
  edits1[word_] := With[{c = chars[word]}, join @@@ Join[
     Table[
      rep[c, c, #, rev[#]] &@{{i}, {i + 1}}, {i, len[c] - 1}], 
     Table[Delete[c, i], {i, len[c]}], 
     flat[Outer[#1[c, ##2] &, {ins[#1, #2, #3 + 1] &, rep}, 
       alphabet, Range[len[c]], 1], 2]]];
  max[set_] := Sort[Map[{nwords[#], #} &, set]][[-1, -1]];
  known[words_] := inter[words, keys[nwords]]]; 
 knownEdits2[word_] := known[flat[Nest[Map[edits1, #, {-1}] &, word, 2]]];
 corrector[word_] := max[listOr[known[{word}], known[edits1[word]],
   knownEdits2[word], {word}]];]];

您需要一些训练文本作为字符串传递给函数的第二个参数,而第一个参数是纠错器的函数名。这是Norvig使用的一个例子:

text = Import["http://norvig.com/big.txt", "Text"];

你只需调用一次,例如:
In[7]:= makeCorrector[correct, text]

然后在一些单词上使用它任意次数

In[8]:= correct["coputer"] // Timing

Out[8]= {0.125, "computer"}

你可以创建自定义的类似于With的控制结构,其中硬编码一些最让你感到烦恼的长名称的简称,并将其包装在你的代码片段周围(但是您将失去代码高亮)。请注意,我通常不提倡此方法 - 我只是为了好玩和减少代码行数而这样做。但至少,它在交互式和包中都能够工作。无法使用中缀运算符,无法更改优先级等等,但几乎没有任何工作量。

在笔记本界面中,CellEvaluationFunction钩子可以用于这种处理。请参见我在此主题上的一个答案 - WReach
有人给了我一个关于“符号”/包问题的讨论链接,但是我把它放错了地方。在我找到它之前,您知道是否有人尝试通过将包作为字符串加载并使用FrontEnd处理它的解决方法吗? - Mr.Wizard
这里有一个相关问题的链接,你可能会感兴趣:http://groups.google.com/group/comp.soft-sys.math.mathematica/browse_thread/thread/c000439b48751078。关于将其作为字符串加载 - 这可能有效,但由于包解析的方式,也可能存在一些复杂性。我能够使它适用于代表Mathematica单元测试的.mt包,但其中一个问题可能是包导入 - 显式或隐藏对Needs的调用。我还有一种感觉,使用FrontEnd处理包作为一种通用方法在概念上是错误的。 - Leonid Shifrin
@WReach 非常感谢提供链接,它会让我的生活变得更轻松。对我来说,这是一块遗失的拼图。 - Leonid Shifrin

3

这不是一个完整的答案,只是展示我学到的技巧这里(更多涉及符号重新定义而非符号约定,我认为):

Unprotect[Fold];
Fold[f_, x_] :=
  Block[{$inMsg = True, result},
    result = Fold[f, First@x, Rest@x];
    result] /; ! TrueQ[$inMsg];
Protect[Fold];

Fold[f, {a, b, c, d}]
(*
--> f[f[f[a, b], c], d]
*)

编辑

感谢@rcollyer提供以下信息(请参见下面的评论)。

您可以通过使用$inMsg变量随意切换定义:

$inMsg = False;
Fold[f, {a, b, c, d}]
(*
->f[f[f[a,b],c],d]
*)

$inMsg = True;
Fold[f, {a, b, c, d}]
(*
->Fold::argrx: (Fold called with 2 arguments; 3 arguments are expected. 
*)

Fold[f, {a, b, c, d}]

这在测试时非常有价值。

2
+1,这是一种执行区块的形式之一,还有其他名称的变体:RAII指针执行区块。我的OpenAndRead方法对于文件流也是同样的内容。 - rcollyer
1
@Simon 重命名似乎并不必要(除了为了可读性的原因),因为 $inMsg 从未在块作用域之外定义。 - Sjoerd C. de Vries
1
@rcollyer 啊哈! 所以它使用 Block[ ] 而不是 Module[ ] ... 来引用同一个变量 :D - Dr. belisarius
2
@belisarius,@rcollyer 我仍然会将Module[{inFunction}, ...]包裹在所有代码周围,例如Module[{inFunction},f[x_]:=Block[{inFunction = True}, codeBefore;f[x];codeAfter]/;!TrueQ[inFunction]]。因为如果你不这样做,并且你使用这个技巧来处理多个函数并忘记使用不同的 $inMsg 符号(或者它们恰好被全局定义),你就会遇到非常微妙的错误,那时候你只能祝你好运调试了。在定义时使用 Module 可以保证每个函数的符号都是唯一的,而且不容易在全局范围内使用。 - Leonid Shifrin
1
@belisarius 你可以定义三个函数 -redefredefOnredefOff,并让它们共享一个本地符号。调用redef[f,codeBefore,codeAfter]redef必须是HoldRestHoldAll)将生成上述修改后的f的定义,以及另外两个定义(都在外部的Module内):redefOn[f]: = inFunction = True; redefOff[f]: = inFunction = False。通过这种方式,您以受控的方式将Module生成的触发变量暴露给顶层。 - Leonid Shifrin
显示剩余10条评论

3

(我的第一次回复/发帖...请温柔些)

根据我的经验,该功能似乎是一个编程死胡同。定义自定义符号的能力似乎严重依赖于使用“符号面板”来定义和清除每个自定义符号。(“一切都是表达式”...好吧,除了一些模糊的情况,比如符号,你必须使用面板。)很遗憾。

符号包文档明确提到了这一点,所以我不能太抱怨。

如果您只想在特定笔记本中定义自定义符号,则Notations可能对您有用。另一方面,如果您的目标是在YourOwnPackage.m中实现自定义符号并将其分发给其他人,则可能会遇到问题。(除非您非常熟练地掌握Box结构?)

如果有人能纠正我的无知,那将使我感到非常高兴! :)

(我希望使用Notations强制MMA将带下标的变量视为符号。)


1
欢迎来到StackOverflow。感谢您分享您的经验。您并不是第一个建议有Notation定义和包存在问题的人。直到现在,我只对不需要Notation$PreRead(例如双参数的Fold)进行了一些限制修改,但我想探索这些方法的能力和局限性。 - Mr.Wizard
@telefunkenvf14 你好!我想我在mathgroup上遇到过你几次,对吧?欢迎你来这里。 - Sjoerd C. de Vries
@telefunkenvf14 哈!你的昵称让我想起了这里的老式音频黑客。欢迎。 - Dr. belisarius
1
@telefunkenvf14 您可以通过以下方式强制MMA将带下标的变量视为符号:Needs["Notation``"]; Symbolize[ParsedBoxWrapper[SubscriptBox["_", "_"]]]。您必须使用Notation包,但不需要使用调色板。(在“Needs”中使用一个反引号--无法确定markdown)。 - WReach
@WReach:如果您需要在代码中使用反引号,请使用双反引号标记代码:即Needs["Notation`"]。(当然,如果您需要一个反引号和双反引号怎么办?) - Simon
显示剩余3条评论

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