Clojure中的解构是什么意思?

6

我是一名Java程序员,正在学习Clojure。

Clojure中的解构是什么?

我看到这篇博客说:

解构的最简单示例是分配一个向量的值。

user=> (def point [5 7])
#'user/point

user=> (let [[x y] point]
         (println "x:" x "y:" y))
x: 5 y: 7

他所说的“给向量分配值”是什么意思?它的真正用途是什么?谢谢。
6个回答

2

point是一个包含值向量的变量。[x y]是变量名的向量。

当你将point分配给[x y]时,解构意味着每个变量都会被赋予相应值中的相应元素。

这只是一个更简单的写法:

(let [x (nth point 0) y (nth point 1)]
    (println "x:" x "y:" y))

请参见Clojure let绑定形式,了解另一种使用解构的方式。

我来自Java背景,这是不是像基于旧变量创建新变量? - sriram
我不太了解Java,但我认为它没有类似的东西。它不是创建新变量,而只是从另一个变量的不同部分绑定多个变量的语法糖。 - Barmar
@sriram 这就像基于旧值创建新的本地变量,但是和 Java 的 final 一样,它们是不变的。任何带有参数的 letfn 都可以实现这个功能——而不仅仅是具有解构的函数。 - Thumbnail

2
术语“解构”听起来比它实际上要复杂。它就像是将形状与形状进行视觉匹配。例如:
(def nums [1 2 3 4 5 6])

(let [[a b c & others] nums]
  ;; do something
  )

Imagine the effect of the let binding as:

 1  2  3    4 5 6
 |  |  |   (     )
 v  v  v      v
[a  b  c & others]

;; Now we can use a, b, c, others, and of course nums, 
;; inside the let binding:

user=> (let [[a b c & others] nums] 
         (println a) 
         (println b) 
         (println c) 
         (println others) 
         (println nums))      
 1
 2
 3
 (4 5 6)
 [1 2 3 4 5 6]

目标是为了在let绑定或函数(即“词法作用域”内)的范围内,简洁地命名集合中的项。

为什么要“简洁”?如果没有解构,let绑定看起来会像这样:

(let [a (nth nums 0) ;; or (first nums)
      b (nth nums 1) ;; or (second nums)
      c (nth nums 2)
      others (drop 3 nums)]
  ;; do something
  )

这里是基本思想的说明。虽然有很多细节(如果和但是,要注意和不要注意),但值得深入阅读了解。以下是一些更详细且带有示例的资源:
我个人最喜欢Jay Fields在Clojure Destructuring上的发布: http://blog.jayfields.com/2010/07/clojure-destructuring.html 来自Braveclojure的简明介绍: http://www.braveclojure.com/do-things/#3_3_3__Destructuring

2
这意味着使用符号制作一张数据结构的图片。
((fn [[d [s [_ _]]]] 
  (apply str (concat (take 2 (name d)) (butlast (name s)) (drop 7 (name d))) ))     
   '(describing (structure (of data))))

=> "destructuring"

((fn [[d e _ _ _ _ _ i n g _ _ _ _ _ s t r u c t u r e & etc]] 
  [d e s t r u c t u r i n g]) "describing the structure of data")

=> [\d \e \s \t \r \u \c \t \u \r \i \n \g]

将这些^示例粘贴到REPL中,并尝试操作它们,以查看其工作原理。

我喜欢那个第一句话:生动而简洁。该死!为什么我没想到说那个。 - Thumbnail

0
解构绑定是通过将每个名称绑定到对象的相应部分来将名称模式绑定到复杂对象的过程。
要绑定到一个序列,您需要提供一个名称向量。例如...
(let [[x y] (list 5 7)] ... )

...等同于

(let [x 5, y 7] ... )

要通过索引查找绑定到映射向量,您需要提供一个名称到键值对的映射。例如...
(let [{x 0, y 1} [5 7]] ... )

...等价于上述两者。

正如其他人所提到的,您可以在此处找到有关这种强大机制的完整描述。


0

它用于命名数据结构的组件并获取它们的值。

比如你想要一个“人”结构。在Java中,你需要创建一个类,包括构造函数、各种字段的getter和setter,例如姓名、年龄、身高等。

在Clojure中,你可以跳过这些“仪式”,只需使用一个有3个插槽的向量,第一个是姓名,第二个是年龄,最后一个是身高。现在你可以简单地为这些“组件”命名并获取它们的值,就像这样:

(def person ["Fred" 30 180])
(let [[name age height] person]
  (println name age height))    ;; will print: Fred 30 180

附言 - 在Clojure中有更好的方法来创建“person”(例如记录等),这只是一个例子,以便了解解构的作用。


0

解构是一种方便的特性,它允许通过拆分复杂的数据结构(如向量等可序列化对象或哈希映射等可关联对象)轻松创建本地绑定(不是变量!),如此处所述。

以以下示例为例:

(let [v [1 2 3 4 5 6]
      v_0 (first v)
      v_1 (nth v 1)
      v_rest (drop 2 v) 
      m {:a 1 :b 2}
      m_a (get m :a)
      m_b (get m :b)
      m_default (get m :c "DEFAULT")]
  (println v, v_0, v_1, v_rest, m, m_a, m_b, m_default))

然后,可以使用解构绑定来简化上述代码,如下所示:

(let [[v_0 v_1 & v_rest :as v]
      [1 2 3 4 5 6]
      {m_a :a m_b :b m_default :c :or {m_default "DEFAULT"} :as m}
      {:a 1 :b 2}]
  (println v, v_0, v_1, v_rest, m, m_a, m_b, m_default))

解构模式可用于let绑定和函数参数(fndefnletfn等),还可在宏中使用,以返回包含这种解构模式的let绑定。

需要注意的一个重要用法是在if-letwhen-let宏中。即使解构绑定本身计算结果为nilif语句也总是对整个形式进行评估:

(if-let [{:keys [a b]}
        {:c 1 :d 2}]
  (println a b)
  (println "Not this one"))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接