为什么在Clojure中没有多个返回值

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么在Clojure中没有多个返回值相关的知识,希望对你有一定的参考价值。

在Clojure中缺乏多重回报价值支持的理由是什么? (Clojure似乎与Common Lisp的values / multiple-value-bind没有任何相似之处)

在函数式编程风格中,显式解构被认为是更惯用的,它是否与JVM有关,还是仅仅被认为是太复杂而没有获得太少的收益?

答案

听起来你已经熟悉了矢量和贴图的基本Clojure解构:

(defn calc-sqr-vec [x]
  [ x  (* x x) ])         ; returns a vector of 2 values

(defn calc-sqr-map [x]
  { :x x  :y (* x x) })   ; returns a map of 2 entries

(let [ [x y] (calc-sqr-vec 3) ]
  (println (format "3^2 -> [%d,%d]" x y)))

(let [ {:keys [x y]} (calc-sqr-map 3) ]
  (println (format "3^2 -> [%d,%d]" x y)))

vec: 3^2 => [3,9]
map: 3^2 => [3,9]

在单个向量或映射中包装两个返回值xy的位置,调用者在需要时拉出组件值。

我无法回答与CL相关的why部分,但与Python样式的多个返回值相比,一个很大的好处是当用户没有指定所有返回值时该怎么做的问题。例如:

q, r = divmod(22, 7)
q => 3
r => 1

q = divmod(22, 7)
q => (3,1)

因此,在Python中,相同的表达式divmod(22, 7)会生成不同的结果,这些结果取决于语句的“接收”部分。通过始终返回相同的单个值并允许调用者选择何时以及如何提取所需位(并忽略不需要的位)来避免这种类型的复杂性。


更新

有趣的是,今天这个主题来了,因为就在昨天我正在使用需要返回一堆单独值的函数。我写了一个简短的宏来使它更容易。单元测试显示它在行动:

(dotest
  (let [some-fn (fn []
                  (let [a 1
                        b 2
                        c 3
                        d 4
                        e 5]
                    (label-value-map a b c d e))) ]
    (is= {:a 1 :b 2 :c 3 :d 4 :e 5} (some-fn))
    (let [ {:keys [a b c d e]} (some-fn) ]
      (is= [a b c d e] [1 2 3 4 5]))))

所以使用label-value-map和普通的{:keys [a b c d e]}解构,你可以将一堆标量值从一个地方转移到另一个地方,而不是打字。 :)

另一答案

我认为答案的原因是因为它不需要多值支持,原因是不需要一个是因为可以用宏来轻松编写。我主要在Common Lisp工作,并且知道一些Clojure,但它似乎可能是正确的,因为它更加惯用。 Clojure里面有更多包括对地图和矢量的支持以及解构它们比Common Lisp更多,所以它更容易,更安全,并且可能更有效地手动解构而不是包括multiple-value-bindvalues的版本。

在旁注中,我很少使用valuesmultiple-value-bind,除非它更具惯用意义,函数会“自然地”返回单个值,就像(mod 5 4)的情况一样,有时我只想要第二个值。

另一答案

我们可以推测出原因,但是考虑到Clojure在这个领域的功能,我想我们可以说它根本不需要多个返回值。

开箱即用,有两种在Clojure中模拟多个返回的基本方法:用于解构的复合值和动态绑定的辅助返回。

这是解构风格,所有结果都是热切地返回,并且可以根据需要由调用者进行解构:

(defn foo
  "Returns a map with results at keys :ret and :aux."
  []
  {:ret :primary, :aux :auxiliary})

(let [{:keys [ret]} (foo)]
  (vector ret))                 ; => [:primary]
(let [{:keys [ret aux]} (foo)]
  (vector ret aux))             ; => [:primary :auxiliary]

这是动态var辅助返回样式,其中可选结果是opt-in(调用者信号感兴趣)并通过side channel返回:

(def ^:dynamic *foo-aux* nil)

(defn foo
  "Returns result, and auxiliary result in *foo-aux* if bound."
  []
  (when (thread-bound? #'*foo-aux*)
    (set! *foo-aux* :auxiliary))
  :primary)

(vector (foo))                ; => [:primary]
(binding [*foo-aux* nil]
  (let [ret (foo)]
    (vector ret *foo-aux*)))  ; => [:primary :auxiliary]

根据样式,结果会被计算并急切地返回,或者由调用者明确询问并因此按需计算。两种风格都允许呼叫者选择单点结果。

以上是关于为什么在Clojure中没有多个返回值的主要内容,如果未能解决你的问题,请参考以下文章

Clojure:让作用域和函数返回值

Clojure布尔值true或false

在没有嵌套 let 的情况下在 Clojure 中绑定多个相关变量

clojure 函数,左值和返回值

如何将 clojure 函数作为字符串返回

Clojure / Clojurescript:按多个值的地图分组