Clojure:解构
在The Joy of Clojure(TJoC)中,destructuring被描述为 Clojure中的一种迷你语言。学习这种迷你语言并不是必须的;但是,正如TJoC的作者所指出的,析构有利于简明、优雅的代码。
让代码更容易理解
对于那些刚刚开始学习编码的人来说,最可怕的事情之一是他们必须努力弄清楚一套看似不可能的规则和结构对他们正在尝试的工作意味着什么。这一点都不容易,很多人都在大肆挣扎。
幸运的是,有一些人正在进行解构代码的过程,以便将其分解成更小、更容易管理的块状。如果这要发生,那么人们可以很容易地看到他们如何有可能从编码的过程中获得更多的价值,甚至如何在未来为自己作出贡献。
我们需要尽可能地鼓励下一代的编码员,因为毫无疑问,他们最终会对 编码的未来如何决定产生巨大的影响。如果他们能最好地理解编码并理解其许多错综复杂的问题,那么他们将能够顺利地处理它。然而,我们需要在这个过程中支持和鼓励他们,而这一切都要从让编码更容易理解开始。
什么是去结构化?
Clojure支持抽象的结构性绑定,通常称为析构,在let绑定列表、fn参数列表,以及任何扩展为let或fn的宏中。 --clojure.org/special_for…
解构的最简单的例子是对一个向量的值进行分配:
user=> (def point [5 7])
#'user/point
user=> (let [[x y] point]
(println "x:" x "y:" y))
x: 5 y: 7
注:在我的去结构化的例子中,我使用了let;然而,在实践中,我倾向于在函数参数列表中至少使用去结构化,如果不是更频繁的话。
我承认,我不记得曾经像第一个例子那样使用过析构,但这是一个很好的起点。一个更现实的例子是将一个向量分割成头和尾。当用arglist定义一个函数时,你要用一个安培号。在 去结构化中也是如此。
user=> (def indexes [1 2 3])
#'user/indexes
user=> (let [[x & more] indexes]
(println "x:" x "more:" more))
x: 1 more: (2 3)
同样值得注意的是,你可以使用:as指令将整个向量绑定到一个局部:
user=> (def indexes [1 2 3])
#'user/indexes
user=> (let [[x & more :as full-list] indexes]
(println "x:" x "more:" more "full list:" full-list))
x: 1 more: (2 3) full list: [1 2 3]
矢量的例子是最简单的;然而, 在实践中,我发现自己更经常使用地图的析构。
在地图上进行简单的析构,就像选择一个局部名称和提供键一样简单:
user=> (def point {:x 5 :y 7})
#'user/point
user=> (let [{the-x :x the-y :y} point]
(println "x:" the-x "y:" the-y))
x: 5 y: 7
如例子所示,:x和:y的值被绑定到名称为the-x和the-y的局部。在实践中,我们永远不会把 "the-"加到我们的局部名称中;然而,使用不同的名称为我们的第一个例子提供了一点明确性。在生产代码中,你更可能需要与键同名的locals。正如下一个例子所显示的那样, 这样做效果非常好:
user=> (def point {:x 5 :y 7})
#'user/point
user=> (let [{x :x y :y} point]
(println "x:" x "y:" y))
x: 5 y: 7
虽然这样做效果很好,但创建与键同名的locals会变得很乏味和烦人(特别是当你的键长于一个字母时)。Clojure预见到了这种挫折感,并提供了:keys指令,允许你指定你想作为locals的同名键:
user=> (def point {:x 5 :y 7})
#'user/point
user=> (let [{:keys [x y]} point]
(println "x:" x "y:" y))
x: 5 y: 7
有几个指令可以在解构地图时发挥作用。上面的例子显示了:key的使用。在实践中,我最常使用的是:key;然而,在处理map时,我也曾使用过:as指令。
下面的例子说明了如何使用 :as 指令来绑定一个局部和整个地图。
user=> (def point {:x 5 :y 7})
#'user/point
user=> (let [{:keys [x y] :as the-point} point]
(println "x:" x "y:" y "point:" the-point))
x: 5 y: 7 point: {:x 5, :y 7}
我们现在已经看到:as指令被用于向量和地图。在这两种情况下,locale总是被分配给正在被解构的整个表达式。
为了完整起见,我将记录 :or 指令;但是,我必须承认,我在实践中从未使用过它。:or指令是用来在被解构的映射不包含指定的键时分配默认值的。
user=> (def point {:y 7})
#'user/point
user=> (let [{:keys [x y] :or {x 0 y 0}} point]
(println "x:" x "y:" y))
x: 0 y: 7
最后,值得注意的是,你可以对嵌套地图、向量和两者的组合进行解构。
下面的例子是对一个嵌套地图的解构
user=> (def book {:name "SICP" :details {:pages 657 :isbn-10 "0262011530"}})
#'user/book
user=> (let [{name :name {pages :pages isbn-10 :isbn-10} :details} book]
(println "name:" name "pages:" pages "isbn-10:" isbn-10))
name: SICP pages: 657 isbn-10: 0262011530
正如你所期望的,你也可以在重组嵌套地图时使用指令:
user=> (def book {:name "SICP" :details {:pages 657 :isbn-10 "0262011530"}})
#'user/book
user=>
user=> (let [{name :name {:keys [pages isbn-10]} :details} book]
(println "name:" name "pages:" pages "isbn-10:" isbn-10))
name: SICP pages: 657 isbn-10: 0262011530
解构嵌套的向量也是非常直接的,正如下面的例子所示
user=> (def numbers [[1 2][3 4]])
#'user/numbers
user=> (let [[[a b][c d]] numbers]
(println "a:" a "b:" b "c:" c "d:" d))
a: 1 b: 2 c: 3 d: 4
由于绑定形式可以任意地在彼此之间嵌套,所以你几乎可以把任何东西拉开 --clojure.org/special_for…
下面的例子同时解构了一个地图和一个向量:
user=> (def golfer {:name "Jim" :scores [3 5 4 5]})
#'user/golfer
user=> (let [{name :name [hole1 hole2] :scores} golfer]
(println "name:" name "hole1:" hole1 "hole2:" hole2))
name: Jim hole1: 3 hole2: 5
同样的例子可以用一个函数定义重写,以显示在参数列表中使用解构的简单性:
user=> (defn print-status [{name :name [hole1 hole2] :scores}]
(println "name:" name "hole1:" hole1 "hole2:" hole2))
#'user/print-status
user=> (print-status {:name "Jim" :scores [3 5 4 5]})
name: Jim hole1: 3 hole2: 5