【问题标题】:May I write {x,a,b}//Do[...,#]& instead of Do[...,{x,a,b}]?我可以写 {x,a,b}//Do[...,#]& 而不是 Do[...,{x,a,b}] 吗?
【发布时间】:2011-08-17 15:38:02
【问题描述】:

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

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

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

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

  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}];,这意味着......见鬼,他们认为它易于阅读/支持/等等?!

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

【问题讨论】:

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


【解决方案1】:

您建议的样式通常是可能的,但在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
  • 我以为你会这么说:)
【解决方案2】:

我认为您需要学习使用 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

【讨论】:

  • +1,用于促进中缀形式,这通常有助于提高可读性。
【解决方案3】:

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

在您的示例的特定情况下,一般来说,我想提请注意其他人没有提到的一些事情。首先,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

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

【讨论】:

  • +1 因为这些都是好点。不过,我怀疑任何了解足够多的 mma 编程来尝试做类似您的 Unevaluated 示例的人都会很难弄清楚发生了什么。这当然是你开始的重点:只要你知道自己在做什么,就可以做任何你想做的事情。
  • @acl 有一些没有Unevaluated 的真实例子很重要并且会让人感到困惑。例如:x=1;With[{x = Pi}, #]&[Sin[x]].
  • 这并不让我感到惊讶。我希望Sin[x] 在被传递到With 之前被评估,因为f[#]&[x] 形式将f 的任何属性应用于# 而不是x,或者更简洁地说,x 是一个参数f[#]& 不是 f
  • @rcollyer 不是每个人都这么先进。很多人对此感到惊讶,并且声称f[x]f[#]&[x] 等效的文档使问题变得更糟。
  • @rcollyer 我可以确认@acl 的结论:Set 确实做得很好,因为HoldFirst,结果(Pi) 是With 在评估到来时看到的给它(Set 返回 r.h.s. 的评估结果)。完全等同于使用以下代码:x = 1; x = Pi; With@@{{Pi},Sin[x]}.
【解决方案4】:

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

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

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

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

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

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

指定List 的哪个元素对应于适当的参数。我倾向于这样做,但你可以将它与Apply 结合起来使用

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

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

编辑:我应该指出,上面的fg 只是占位符,它们可以并且经常被替换为纯函数,例如#+1& @ x 大部分等同于#+1&[x],参见Leonid's answer

澄清一下,根据Leonid's answer,如果f 不具有attributef@exprf[expr] 之间的等价性为真,这将阻止表达式expr 在通过之前被评估到f。例如,AttributesDo 之一是 HoldAll,这允许它充当范围构造,允许在内部评估其参数而无需撤消外部影响。关键是expr 将在传递给f 之前进行评估,因此如果您需要它保持不评估状态,则必须格外小心,例如使用Hold style attribute 创建一个纯函数。

【讨论】:

    【解决方案5】:

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

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

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

    如果我有一个列表,比如lst,而我现在专注于制作一个复杂的情节,我会这样做

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

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

    f[
        g[
            a,
            b,
            c
        ]
    ]
    

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

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

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

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

    【讨论】:

    • 我用的和你一样的布局,但是你真的觉得重组代码更费劲吗?当您使用回车换行时,我发现 mma 缩进完美。所以,我所要做的就是输入一些回报,我就完成了。作为一个额外的好处,如果格式化的行为很有趣,那通常是某种语法错误的提示。
    • @Sjoerd 我同意你的 cmets,而且你可以重新定义缩进行为特别好(我有调色板让我可以即时更改)。但是当我同时使用前端和文本编辑器时,我不依赖即时缩进,而是手动将其放入,并关闭自动的东西。然后我可以按照我的意愿构建所有内容,但没有工具来增加/减少缩进级别(比如说)。无论如何,我可能表达得不太好,但我发现在 emacs 或我的 python IDE 中编码的体验更加愉快,因为我可以有效地构建我的代码
    • (续)当然,前端具有在mathematica中可编程的优势,但我发现我通常只使用工作台来代替任何冗长的东西。事实上,有时我在终端中用 emacs 编写代码,尽管代码是“惰性的”,但在某些方面它比前端更容易延展。但无论如何,这些都是品味。
    • 那么语法着色呢?
    • @Sjoerd 怎么样?我不明白这个问题......除非你的意思是例如 emacs,答案是 a) 有一种模式可以为 emacs 语法颜色 mma 代码(当然!),b) 我不喜欢它前端或工作台,我只是觉得它的一些方面更令人愉快(例如,能够轻松增加块的缩进)。但是,如果我在家里通过 ssh 使用其中一台功能强大的机器,我将不得不在终端中通过 emacs 编辑 mma 代码,不间断地 scp 事物,或者第一次输入完美的代码 :)
    【解决方案6】:

    对于单参数函数(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"}
    

    【讨论】:

    • 完全等价的陈述只适用于独立的函数调用。在更大的表达式中,这些形式并不完全等价,因为它们具有不同的优先级。使用Sequence 与纯函数调用不同,因为序列 - 拼接是运行时而不是解析时效果。除了您提到的可能问题之外,Sequence - 拼接会导致非零运行时开销。我不会在这种情况下使用它。我相信你知道这一切,所以评论是为你的答案的读者准备的。
    • 这也不完全正确。例如,尝试 ff@(arg)[[1]]ff[arg][[1]]。这是正确的,我想:(f@arg)f[arg](arg//f) 都是一样的。
    • @Alexey 试试Precedence[Plus]
    • (应该提到我在@TomD 对stackoverflow.com/q/6708591/559318 的评论中提到了Precedence
    • @Alexey 这是一个很棒的列表。 (如果有人感兴趣,我在 Kris Carlson 的 A New Mathematica Programming Style 演示文稿中发现了 Precedencehere)。
    猜你喜欢
    • 1970-01-01
    • 2019-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-04-11
    • 1970-01-01
    • 1970-01-01
    • 2011-08-10
    相关资源
    最近更新 更多