从中缀转换为前缀表示法

时间:2012-04-24 00:01:30

标签: oop clojure prefix infix-notation

我最近开始学习Clojure。通常它看起来很有趣,但我不能习惯一些语法上的不便(与之前的Ruby / C#体验相比)。

嵌套表达式的前缀表示法。在Ruby中,我习惯于编写复杂的表达式,并将它们从左到右链接起来:some_object.map { some_expression }.select { another_expression }。当您逐步从输入值移动到结果时,它非常方便,您可以专注于单个转换,而无需在键入时移动光标。与在Clojure中编写嵌套表达式时的情况相反,我将内部表达式的代码编写到外部,我必须不断地移动光标。它会减慢并分散注意力。我知道->->>宏,但我注意到它不是惯用语。当你开始在Clojure / Haskell等编码时,你有同样的问题吗?你是怎么解决的?

4 个答案:

答案 0 :(得分:9)

我最初对Lisps有同感,所以我感到痛苦: - )

然而,好消息是你会发现,经过一段时间和常规使用,你可能会开始喜欢前缀表示法。事实上,除了数学表达式之外,我现在更喜欢使用中缀样式。

喜欢前缀表示法的原因:

  • 与函数的一致性 - 大多数语言使用混合的中缀(数学运算符)和前缀(函数调用)表示法。在Lisps中,如果你认为数学运算符是函数,它就是一致的,具有一定的优雅性
  • - 如果函数调用始终位于第一个位置,则会变得更加清晰。
  • Varargs - 很高兴能够为几乎所有运营商提供可变数量的参数。 (+ 1 2 3 4 5)1 + 2 + 3 + 4 + 5
  • 更擅长恕我直言

然后,一个技巧就是使用->->> librerally,这样就可以通过这种方式构建代码。这在处理对象或集合的后续操作时通常很有用,例如

(->>
  "Hello World" 
  distinct 
  sort 
  (take 3))

==> (\space \H \W)

我在前缀样式中工作时发现的最后一个技巧是在构建更复杂的表达式时充分利用缩进。如果你正确地缩进,那么你会发现前缀表示法实际上很清楚:

(defn add-foobars [x y]
  (+
    (bar x y)
    (foo y)
    (foo x)))

答案 1 :(得分:4)

据我所知,->->>在Clojure中是惯用的。我一直都在使用它们,在我看来,它们通常会带来更易读的代码。

以下是来自Clojure“生态系统”周围的热门项目中使用的这些宏的一些示例:

以身作则证明:)

答案 2 :(得分:3)

当我第一次开始使用口齿不清时,我确实看到了同样的障碍,直到我看到它使代码更简单,更清晰的方式才真正令人讨厌,一旦我理解了烦恼消退的好处

initial + scale + offset

成了

(+ initial scale offset)

然后尝试(+)前缀表示法允许函数指定自己的标识值

user> (*)
1
user> (+)
0

还有更多的例子,我的观点是不要捍卫前缀表示法。我只希望传达学习曲线在情绪上变得平坦,因为积极的一面变得明显。

当然,当你开始编写宏时,前缀表示法变为必须的而不是方便。


为了解决你问题的第二部分,线程优先和线程最后的宏是惯用的,只要它们使代码更清晰:)它们通常用于函数调用而不是纯算术,尽管没有人会因为你使用它们而错误地使用它们他们使这个等式变得更加可口。


ps:(.. object object2 object3) - > object().object2().object3();

(doto my-object
   (setX 4)
   (sety 5)`

答案 3 :(得分:3)

如果您有长表达链,请使用let。长失控表达式或深层嵌套表达式在任何语言中都不是特别易读。这很糟糕:

(do-something (map :id (filter #(> (:age %) 19) (fetch-data :people))))

这稍微好一些:

(do-something (map :id
                   (filter #(> (:age %) 19)
                           (fetch-data :people))))

但这也很糟糕:

fetch_data(:people).select{|x| x.age > 19}.map{|x| x.id}.do_something

如果我们正在读这篇文章,我们需要知道什么?我们在do_something的某个子集的某些属性上调用people。这段代码难以阅读,因为在第一个和最后一个之间有这么大的距离,我们忘记了我们在它们之间旅行时所看到的东西。

在Ruby的情况下,do_something(或任何产生我们最终结果的东西)在行尾都会丢失,所以我们很难说我们对people做了什么。在Clojure的情况下,很明显do-something就是我们正在做的事情,但是如果不将整个内容读到内部,很难说出我们正在做什么。

任何比这个简单例子更复杂的代码都会变得非常痛苦。如果你的所有代码都是这样的话,你的脖子就会厌倦来回扫描所有这些意大利面条。

我更喜欢这样的事情:

(let [people (fetch-data :people)
      adults (filter #(> (:age %) 19) people)
      ids    (map :id adults)]
  (do-something ids))

现在很明显:我从people开始,我四处游荡,然后我do-something给他们。

你可能会侥幸逃脱:

fetch_data(:people).select{|x|
  x.age > 19
}.map{|x|
  x.id
}.do_something

但我可能宁愿这样做,至少:

adults = fetch_data(:people).select{|x| x.age > 19}
do_something( adults.map{|x| x.id} )

即使你的中间表达没有好名字,使用let也并非闻所未闻。 (这种风格偶尔会用在Clojure自己的源代码中,例如defmacro的源代码)

(let [x (complex-expr-1 x)
      x (complex-expr-2 x)
      x (complex-expr-3 x)
      ...
      x (complex-expr-n x)]
  (do-something x))

这对调试很有帮助,因为您可以通过以下方式随时检查事物:

(let [x (complex-expr-1 x)
      x (complex-expr-2 x)
      _ (prn x)
      x (complex-expr-3 x)
      ...
      x (complex-expr-n x)]
  (do-something x))