8

我爱上了鲁比。在这种语言中,所有核心功能实际上都是方法。这就是为什么我更喜欢后缀符号的原因——当我要处理的数据放在匿名处理函数体的左侧时,例如:array.map{...}. 我相信,它的优势在于这段代码的易读性。

但是 Mathetica 具有功能性(是的,如果您愿意,它可以是程序性的)决定了一种样式,其中函数名称位于数据的左侧。正如我们在其手册中看到的那样,//仅当它是一些简单的函数时使用,没有参数,例如list // MatrixForm. 当 Function 需要大量参数时,编写手册的人会使用 syntax F[data]
没关系,但我的问题就是这种情况F[f,data],例如Do[function, {x, a, b}]。大多数 Mathematica 函数(如果不是全部)的参数完全按照这个顺序 - [function, data],而不是[data, function]. 由于我更喜欢​​使用纯函数来保持命名空间的清洁,而不是在我的笔记本中创建大量命名函数,所以参数function可能太大——太大了,那个参数data将放置在函数调用行之后的第 5-20 行代码中。

这就是为什么有时,当邪恶的 Ruby 天性使我受到控制时,我会以 postfix 的方式重写这些函数:

Do[f (x), {x, a, b}]\n{x, a, b} // Do[f (x), #] &

因为这对我很重要,所以纯函数(可能是大代码)是直接从处理数据中放置的。是的,我做到了,我很高兴。但是有两点:

  1. 这会导致 Mathematica 的高亮解析器问题:xin 后缀符号以蓝色突出显示,而不是青绿色;
  2. 每次我查看 Mathematica 手册时,我都会看到这样的示例:Do[x[[i]] = (v[[i]] - U[[i, i + 1 ;; n]].x[[i + 1 ;; n]])/ U[[i, i]], {i, n, 1, -1}];,这意味着......地狱,他们认为它很容易阅读/支持/等等。?!

所以这两件事让我在这里问这个问题:我是不是很坏,使用我的 Ruby 风格,我应该像这些人那样编写代码,还是可以,我不必担心,应该随心所欲地写?

4

6 回答 6

10

您建议的样式通常是可能的,但在Do. 问题是Do具有属性HoldAll。这很重要,因为循环变量(x在示例中)必须保持未计算并被视为局部变量。要看到这一点,请尝试评估这些表达式:

x = 123;

Do[Print[x], {x, 1, 2}]
(* prints 1 and 2 *)

{x, 1, 2} // Do[Print[x], #]&
(* error: Do::itraw: Raw object 123 cannot be used as an iterator.
   Do[Print[x], {123, 1, 2}]
*)

发生错误是因为纯函数Do[Print[x], #]&缺少HoldAll属性,导致{x, 1, 2}被评估。您可以通过显式定义具有该HoldAll属性的纯函数来解决该问题,因此:

{x, 1, 2} // Function[Null, Do[Print[x], #], HoldAll]

...但我怀疑治疗比疾病更糟糕:)

因此,当一个人使用“绑定”表达式,如Do、等时TableModule与从众一致是最安全的。

于 2011-08-17T16:25:18.733 回答
8

我认为您需要学习使用Mathematica最自然支持的样式。当然有不止一种方法,而且我的代码看起来不像其他人的。尽管如此,如果您继续尝试将Mathematica语法转换为您自己的先入为主的风格,基于不同的语言,我预计您只会继续感到沮丧。

空格并不邪恶,您可以轻松地添加换行符来分隔长参数:

Do[
  x[[i]] = (v[[i]] - U[[i, i + 1 ;; n]].x[[i + 1 ;; n]]) / U[[i, i]]
  , {i, n, 1, -1}
];

f @ x这就是说,我喜欢使用更多我通常看到的前缀 ( ) 和中缀 ( ) 符号来编写x ~ f ~ y,我发现这很有价值,因为很容易确定这些函数分别接收一个和两个参数。这有点不标准,但我认为它不会破坏Mathematica语法的痕迹。相反,我认为它使用语法来发挥优势。有时这会导致语法高亮失败,但我可以忍受:

f[x] ~Do~ {x, 2, 5} 

当使用除标准形式之外的任何东西f[x, y, z](根据需要使用换行符)时,您必须更加小心评估顺序,恕我直言,可读性可能会受到影响。考虑这个人为的例子:

{x, y} // # + 1 & @@ # &

我不觉得这很直观。是的,对于熟悉Mathematica操作顺序的人来说,它是可读的,但我相信它不会提高清晰度。我倾向于//为自然阅读的命名函数保留后缀:

Do[f[x], {x, 10000}] //Timing //First
于 2011-08-17T16:09:44.713 回答
7

我想说,以一种语言B惯用的方式尝试用一种语言编写程序是最大的错误之一A,只是因为你碰巧很了解后者并且喜欢它。借用习语并没有错,但你必须确保对第二语言有足够的理解,这样你才能知道为什么其他人会这样使用它。

在您的示例的特定情况下,一般来说,我想提请注意其他人没有提到的一些事情。首先,Do是一个范围构造,它使用动态范围来定位其迭代器符号。因此,您有:

In[4]:= 
x=1;
{x,1,5}//Do[f[x],#]&

During evaluation of In[4]:= Do::itraw: Raw object 
1 cannot be used as an iterator. >>

Out[5]= Do[f[x],{1,1,5}]

多么惊喜,不是吗。Do当您以标准方式使用时,这不会发生。

其次,请注意,虽然这一事实在很大程度上被忽略了,但并不f[#]&[arg]总是与. 例子:f[arg]

ClearAll[f];
SetAttributes[f, HoldAll];
f[x_] := Print[Unevaluated[x]]

f[5^2]

5^2

f[#] &[5^2]

25

这不会影响您的示例,但是您的用法与受此影响的情况足够接近,因为您操纵了范围。

于 2011-08-17T16:24:27.490 回答
6

Mathematica 支持将函数应用于其参数的 4 种方式:

  1. 标准函数形式:f[x]
  2. 前缀:f@xg@@{x,y}
  3. 后缀:x // f, 和
  4. 中缀:x~g~y相当于g[x,y].

您选择使用哪种形式取决于您,并且通常是一种审美选择,比其他任何事情都重要。在内部,f@x被解释为f[x]. 就我个人而言,我和你一样主要使用后缀,因为我将链中的每个函数都视为一个转换,并且像这样将多个转换串在一起更容易。也就是说,我的代码将随心所欲地使用标准形式和前缀形式,但我倾向于更多地使用标准形式,因为它会唤起对函数参数的包容感。

我对前缀形式有点随意,因为我在Apply( @@) 旁边包含了Prefix( ) 的简写形式@。在内置命令中,只有标准形式、中缀形式,并且Apply允许您轻松地将多个变量传递给您的函数,而无需额外工作。 Apply(eg g @@ {x,y}) 通过用函数替换Head表达式 ( {x,y}) 的 来工作,实际上是用多个变量 ( g@@{x,y} == g[x,y]) 评估函数。

我使用后缀形式将多个变量传递给函数的方法是通过列表。这需要更多的工作,因为我必须写

{x,y} // f[ #[[1]], #[[2]] ]&

指定 的哪个元素List对应于适当的参数。我倾向于这样做,但是您可以将其与Applylike结合使用

{x,y} // f @@ #&

这涉及更少的打字,但当您稍后阅读时可能会更难以解释。

编辑:我应该指出,f以上g只是占位符,它们可以并且经常被纯函数替换,例如#+1& @ x,主要等同于#+1&[x],请参阅Leonid 的回答

为了澄清,根据Leonid 的回答,如果不具有会阻止表达式 ,在传递给 之前被评估的属性f@expr,则和之间的等价f[expr]是真的。例如,其中之一允许它充当范围构造,允许在内部评估其参数而无需撤消外部影响。点 is将在传递给 之前进行评估,因此如果您需要它保持未评估,则必须格外小心,例如创建具有style 属性的纯函数。fexprfAttributesDoHoldAllexprfHold

于 2011-08-17T16:11:38.480 回答
5

正如您显然知道的那样,您当然可以做到。就个人而言,我不会担心手册如何编写代码,只需按照我认为自然且令人难忘的方式编写即可。

但是,我注意到我通常会陷入特定的模式。例如,如果我在一些计算后生成一个列表并顺便绘制它以确保它符合我的预期,我通常会这样做

prodListAfterLongComputation[
    args,
]//ListPlot[#,PlotRange->Full]&

如果我有一个清单,比如说lst,我现在专注于制作一个复杂的情节,我会做

ListPlot[
    lst,
    Option1->Setting1,
    Option2->Setting2
]

所以基本上,任何偶然的并且可能不重要的可读性(我不需要能够立即解析第一个ListPlot,因为它不是那段代码的重点)最终成为后缀,以避免破坏已经 -编写应用它的复杂代码。相反,我倾向于以以后最容易解析的方式编写复杂的代码,就我而言,类似于

f[
    g[
        a,
        b,
        c
    ]
]

即使它需要更多的输入,并且如果不使用 Workbench/Eclipse 插件,那么重组代码的工作量会更大。

所以我想我会用“在考虑到可能需要可读性和可能失去便利性(例如代码突出显示、重构代码的额外工作等)之后,做最方便的事情”来回答您的问题。

当然,如果您是唯一一个使用某段代码的人,那么所有这些都适用;如果还有其他人,那就是另一个问题了。

但这只是一种意见。我怀疑任何人都可能提供比这更多的东西。

于 2011-08-17T16:10:25.750 回答
1

对于单参数函数和(f@(arg)),即使在应用 的属性的意义上也是完全等价的。在多参数函数的情况下,可以编写或具有相同的效果:((arg)//f)f[arg]ff@Sequence[args]Sequence[args]//f

In[1]:= SetAttributes[f,HoldAll];
In[2]:= arg1:=Print[];
In[3]:= f@arg1
Out[3]= f[arg1]
In[4]:= f@Sequence[arg1,arg1]
Out[4]= f[arg1,arg1]

因此,对于喜欢后缀表示法的任何人来说,解决方案似乎是使用Sequence

x=123;
Sequence[Print[x],{x,1,2}]//Do
(* prints 1 and 2 *)

SequenceHold具有属性or的函数可能会出现一些困难HoldAllComplete

In[18]:= Select[{#, ToExpression[#, InputForm, Attributes]} & /@ 
   Names["System`*"], 
  MemberQ[#[[2]], SequenceHold | HoldAllComplete] &][[All, 1]]

Out[18]= {"AbsoluteTiming", "DebugTag", "EvaluationObject", \
"HoldComplete", "InterpretationBox", "MakeBoxes", "ParallelEvaluate", \
"ParallelSubmit", "Parenthesize", "PreemptProtect", "Rule", \
"RuleDelayed", "Set", "SetDelayed", "SystemException", "TagSet", \
"TagSetDelayed", "Timing", "Unevaluated", "UpSet", "UpSetDelayed"}
于 2011-08-17T18:12:02.267 回答