我可以写{x,a,b} //做[...,#]&而不是做[...,{x,a,b}]?

时间:2011-08-17 15:38:02

标签: coding-style wolfram-mathematica postfix-notation

我爱上了Ruby。在这种语言中,所有核心功能实际上都是方法这就是为什么我更喜欢后缀表示法 - 当我想处理的数据放在匿名处理函数体中时,例如:array.map{...}。我相信,这段代码的易读性有很大优势。

但Mathetica,功能正常(是的,如果你想要它可以是程序性的)指示一个样式,其中函数名称放在数据的左边。正如我们在其手册中所看到的,//仅在它是一些简单的函数时使用,没有参数,如list // MatrixForm。当Function需要很多参数时,编写手册的人使用语法F[data] 没关系,但我的问题是F[f,data],例如Do[function, {x, a, b}]。大多数Mathematica函数(如果不是全部)都具有完全按此顺序排列的参数 - [function, data],而不是[data, function]。由于我更喜欢​​使用纯函数来保持命名空间清洁而不是在我的笔记本中创建大量命名函数,因此参数function可能太大 - 如此之大,参数data将放在带有函数调用的行之后的第5-20行代码。

这就是为什么有时候,当 evil Ruby的性质让我受到控制时,我会以后缀的方式重写这些函数:

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

因为它对我来说很重要,所以纯函数(可能是大代码)就是处理数据。是的我做到了,我很高兴。但有两件事:

  1. 这会导致Mathematica突出显示解析器问题:后缀表示法中的x以蓝色突出显示,而不是绿松石;
  2. 每当我查看Mathematica手册时,我都会看到像这样的例子:Do[x[[i]] = (v[[i]] - U[[i, i + 1 ;; n]].x[[i + 1 ;; n]])/ U[[i, i]], {i, n, 1, -1}];,这意味着......他们认为它很容易阅读/支持/等等?!
  3. 所以这两件事让我在这里提出这个问题:我是个坏男孩,使用我的Ruby风格,我应该像这些人一样编写代码吗,还是好的,我不必担心,应该按我喜欢的方式写作?

6 个答案:

答案 0 :(得分: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]

......但我怀疑治愈方法比疾病更糟糕了:)。

因此,当使用“DoTableModule等”绑定“表达式时,最安全的是与牛群保持一致。

答案 1 :(得分: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

答案 2 :(得分: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] NOT 始终与f[arg]相同。例如:

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

f[5^2]

5^2

f[#] &[5^2]

25

这不会影响您的示例,但由于您操作了范围,因此您的使用距离受此影响的情况非常接近。

答案 3 :(得分:6)

Mathematica supports将4个函数应用于其参数的方法:

  1. 标准函数形式:f[x]
  2. 前缀:f@xg@@{x,y}
  3. 后缀:x // f
  4. 中缀:x~g~y,相当于g[x,y]
  5. 您选择使用的形式取决于您,并且通常是一种美学选择,而不是其他任何形式。在内部,f@x被解释为f[x]。就个人而言,我主要使用postfix,就像你一样,因为我将链中的每个函数都视为转换,并且更容易将多个转换串联起来。也就是说,我的代码将充满标准形式和前缀形式,主要取决于奇思妙想,但我更倾向于使用标准形式,因为它唤起了对功能参数的遏制感。

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

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

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

    指定List的哪个元素对应于适当的参数。我倾向于这样做,但你可以将它与Apply类似

    结合起来
    {x,y} // f @@ #&
    

    涉及较少的打字,但在以后阅读时可能更难解释。

    修改:我应该指出上面的fg只是占位符,它们可以并且经常被纯函数替换,例如#+1& @ x大致相当于#+1&[x],请参阅Leonid's answer

    为了澄清,根据Leonid's answer,如果f@expr没有attribute会阻止表达式,则f[expr]f之间的等效性为真, expr,在传递给f之前进行评估。例如,Attributes的{​​{1}}之一是HoldAll,它允许它充当范围构造,允许在内部评估其参数而不会影响外部影响。点Do将在传递给expr之前进行评估,因此如果您需要将其保持无评估,则必须格外小心,例如使用Hold style attribute创建纯函数。

答案 4 :(得分:5)

你肯定可以做到,正如你明显知道的那样。就个人而言,我不会担心手册如何编写代码,只是按照我发现自然和难忘的方式编写代码。

然而,我注意到我通常会陷入明确的模式。例如,如果我在计算后生成一个列表并偶然绘制它以确保它符合我的预期,我通常会这样做

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

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

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

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

f[
    g[
        a,
        b,
        c
    ]
]

即使需要更多的输入,如果没有使用Workbench / Eclipse插件,也会使重组代码变得更加有效。

所以我想我会回答你的问题,“在考虑到可读性的可能性和可能的​​方便性损失之后做最方便的事情,例如代码突出显示,重构代码的额外工作等等”。

当然,如果你是唯一一个使用某段代码的人,那么这一切都适用;如果还有其他人,那就完全是一个不同的问题了。

但这只是一种意见。我怀疑任何人都可以提供更多。

答案 5 :(得分:1)

对于单参数函数(f@(arg))((arg)//f)f[arg]即使在应用f属性的意义上也完全等效。在多参数函数的情况下,可以写f@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 *)

对于具有属性SequenceHoldHoldAllComplete的函数,可能会出现一些问题:

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"}
相关问题