Clojure中缺少多重返回值支持的原因是什么? (Clojure似乎没有任何类似于Common Lisp的东西values
/multiple-value-bind
)Clojure中为什么没有多重返回值
显式解构被认为在功能性编程风格中被认为更习惯,它与JVM有什么关系,还是它被简单地认为是收益太少,太复杂了?
Clojure中缺少多重返回值支持的原因是什么? (Clojure似乎没有任何类似于Common Lisp的东西values
/multiple-value-bind
)Clojure中为什么没有多重返回值
显式解构被认为在功能性编程风格中被认为更习惯,它与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]
,你包裹返回值在单矢量x
& y
或地图,并且调用者在需要时拉出组件值。
我无法回答关于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-bind
和values
版本。
在一个侧面说明,我很少用values
和multiple-value-bind
时,它使更多的惯用意义上的功能将“自然”在(mod 5 4)
,这里只是有时我想第二个值的情况下返回一个值,如除。
宏和解构都不能替代多个值(我现在只是错过了它们)。你甚至可以解释为什么:调用代码可以假装其他值不存在,但特定的调用者可以查找它们,并且特定的调用可以选择性地提供它们。最后是我所需要的,因为只有一个调用者(但是大多数匿名函数只返回核心值)。因为我只有一个调用者,所以我会用奇怪的lambda返回一个带有合适meta的地图,然后让调用者解开它(减慢所有检查!)。 – kennytilton
我们可以推测为什么,但是看看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]
这里是动态无功返回辅助的风格,可选结果选入(呼叫者兴趣的信号),并通过侧通道返回:
根据风格,结果被计算并急切返回,或EXP由呼叫者合法地要求并因此按需求计算。这两种风格都允许呼叫者选择并选择点菜结果。
我会添加到这个返回的元数据:https://clojure.org/reference/metadata –
*你*是一个野人!让我想起C,在那里我们可以选择传递一个指向被调用函数可以写辅助数据的位置的指针。动态变量当然是一种解决方法,抛开所有的荒谬。 – kennytilton
谢谢@DirkGeurs,我错过了。现在我想到了,但我不明白元数据方法对复合值+解构方法会有什么优势 - 它只适用于支持“元”的返回值,比如地图和矢量,所以适用性非常好狭窄,不是吗? – glts
感谢您的详细示例:)在我的情况下,我主要想知道为什么(基本原理)。如果函数没有返回那么多的值,那么Common Lisp的'multiple-value-bind'只是将额外的变量绑定到nil。正如Clojure的解构一样:'(let [[a b c] [1 2]] c)=> nil'。 Python并没有真正的多元返回值 - 它只是具有语法糖,允许人们从元组中省略括号,因此它们看起来不像元组。 'divmod'总是返回一个包含两个值的元组。 'q,r = <返回元组或列表的任何表达式>'是用于解包项目的糖。 – Lassi
与返回的数组和对象相比,多个返回实际上并没有给你任何东西,只是它停留在堆栈上,并没有分配堆内存。这只是一个优化。在方案I中,宁可不使用它们,因为实施比CL更难使用。 – Sylwester
根据用户是否为所有返回值指定了绑定,我不会购买有关返回不同值的参数。 Python中的示例只是显示'divmod'返回一个2元组,'x,y = f(a)'是解构它的语法,'x = f(a)'是绑定整个元组的语法。返回一个2向量的Clojure'divmod'看起来是一样的,只是Clojure解构语法(当然)是不同的。 – amalloy