Clojure 是否需要在元素操作之前/之后强制实现惰性seq?
如果我使用Clojure 是否需要在元素操作之前/之后强制实现惰性seq?,clojure,lazy-evaluation,Clojure,Lazy Evaluation,如果我使用map对特定于惰性序列的每个成员的单个数据结构执行副作用/变异操作,我是否需要(a)首先调用doall,在执行命令式操作之前强制实现原始序列,或者(b)调用doall强制副作用发生,然后再将功能操作映射到结果序列 我认为,当任何序列的元素之间没有依赖关系时,不需要doalls,因为map无法将函数应用于序列的成员,直到生成该序列的maps中的函数应用于先前序列的相应元素。因此,对于每个元素,函数将按正确的顺序应用,即使其中一个函数会产生后续函数所依赖的副作用。(我知道我不能假设在元素b
map
对特定于惰性序列的每个成员的单个数据结构执行副作用/变异操作,我是否需要(a)首先调用doall
,在执行命令式操作之前强制实现原始序列,或者(b)调用doall
强制副作用发生,然后再将功能操作映射到结果序列
我认为,当任何序列的元素之间没有依赖关系时,不需要doall
s,因为map
无法将函数应用于序列的成员,直到生成该序列的map
s中的函数应用于先前序列的相应元素。因此,对于每个元素,函数将按正确的顺序应用,即使其中一个函数会产生后续函数所依赖的副作用。(我知道我不能假设在元素b被修改之前,任何元素a都会被修改,但这并不重要。)
这是正确的吗
这就是问题所在,如果足够清楚,那么就没有必要进一步阅读。其余部分更详细地描述了我正在尝试做的事情
我的应用程序有一系列defrecord结构(“代理”),每个结构都包含一些core.matrix向量(
vec1
,vec2
)和core.matrix矩阵(mat
)。假设为了速度,我决定(破坏性地,而不是功能性地)修改矩阵
程序通过三次调用map
,对每个代理执行以下三个步骤,以将每个步骤应用于每个代理
assoc
从功能上更新每个代理中的向量vec1
mat
(即矩阵将保持不同的状态)assoc
更新每个代理中的向量vec2
persons
是一个序列,可能是惰性的(编辑:添加外部doall
s):
或者:
(doall
(map #(assoc % :vec2 (calc-vec2-from-mat %)) ; update vec2 based on state of mat
(map update-mat-from-vec1! ; modify mat based on state of vec1
(map #(assoc % :vec1 (calc-vec1 %)) persons)))) ; update vec1 from person
请注意,任何代理的状态都不取决于任何其他代理在任何时候的状态。我是否需要添加doall
s
编辑:截至2014年4月16日的答案概述: 我建议阅读所有给出的答案,但它们似乎有冲突。他们没有,我想如果我总结一下主要观点可能会有用: (1) 我的问题的答案是“是”:如果在我描述的过程结束时,一个人导致实现整个延迟序列,那么对每个元素所做的事情将按照正确的步骤顺序(1、2、3)进行。在步骤2之前或之后不需要应用
doall
,在步骤2中,每个元素的数据结构都发生了变化
(2) 但是:这是一个非常糟糕的想法;你在自找麻烦。如果在某个时刻,您无意中在一个时间实现了序列的全部或部分,而不是您最初预期的时间,那么后面的步骤可能会从数据结构中获取在错误的时间(而不是您预期的时间)放置的值。在惰性序列的给定元素实现之前,改变每元素数据结构的步骤不会发生,因此,如果在错误的时间实现它,则可能在以后的步骤中获得错误的数据。这可能是一种很难追踪的bug。(感谢@A.Webb把这个问题说得很清楚。)
map
总是产生一个惰性结果,即使对于非惰性输入也是如此。如果需要强制执行某些必需的副作用(例如,在关闭文件句柄或db连接之前使用文件句柄或db连接),则应在map
的输出上调用doall
(或dorun
,前提是永远不会使用该序列,并且只针对副作用执行映射)
map
始终生成惰性结果,即使对于非惰性输入也是如此。如果需要强制执行某些必需的副作用(例如,在关闭文件句柄或db连接之前使用文件句柄或db连接),则应在map
的输出上调用doall
(或dorun
,前提是永远不会使用该序列,并且只针对副作用执行映射)
您不需要添加任何对
doall
的调用,只要您稍后在程序中对结果进行处理。例如,如果您运行上面的映射,但没有对结果执行任何操作,则所有元素都不会实现。另一方面,如果您通读结果序列,例如打印它,那么您的每个计算都将按顺序在每个元素上按顺序进行。也就是说,步骤1、2和3将发生在输入序列中的第一件事情上,然后步骤1、2和3将发生在第二件事情上,依此类推。无需预先实现序列以确保值可用,惰性评估将解决这一问题 您不需要添加任何对doall
的调用,只要您稍后在程序中对结果进行处理。例如,如果您运行上面的映射,但没有对结果执行任何操作,则所有元素都不会实现。另一方面,如果您通读结果序列,例如打印它,那么您的每个计算都将按顺序在每个元素上按顺序进行。也就是说,步骤1、2和3将发生在输入序列中的第一件事情上,然后步骤1、2和3将发生在第二件事情上,依此类推。无需预先实现序列以确保值可用,惰性评估将解决这一问题 将懒惰与副作用混为一谈时要格外小心
(defrecord Foo [fizz bang])
(def foos (map ->Foo (repeat 5 0) (map atom (repeat 5 1))))
(def foobars (map #(assoc % :fizz @(:bang %)) foos))
那么,我的食物棒的泡沫现在是1吗
(:fizz (first foobars)) ;=> 1
酷,现在我将离开foobars,用我原来的foos
(doseq [foo foos] (swap! (:bang foo) (constantly 42)))
让我们
(:fizz (first foobars)) ;=> 1
(doseq [foo foos] (swap! (:bang foo) (constantly 42)))
(:fizz (first foobars)) ;=> 1
(:fizz (second foobars)) ;=> 42
(defn f1 [x]
(print "1>" x ", ")
x)
(defn f2 [x]
(print "2>" x ", ")
x)
(defn foo [mycoll]
(->> mycoll
(map f1)
(map f2)
dorun))
=> (foo (list \a \b))
1> a , 2> a , 1> b , 2> b , nil
=> (->> (iterate inc 7) (take 2) foo)
1> 7 , 2> 7 , 1> 8 , 2> 8 , nil
=> (foo [\a \b])
1> a , 1> b , 2> a , 2> b , nil
=> (foo (range 50))
(->> [\a \b]
; apply both f1 and f2 before moving to the next element
(map (comp f2 f1))
dorun)
(->> (list \a \b)
(map f1)
; process the whole sequence before applying f2
doall
(map f2)
dorun)