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
    (即矩阵将保持不同的状态)
  • 根据步骤2生成的矩阵状态,使用
    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)