Clojure-懒惰的教训

懒惰可能是一件好事。 也许最好的事情?

图片来源-https://winnin.com/battle/357860-Cute-Animals-Who%27ll-Make-You-Feel-Less-Lazy?force_lang=es

当然,在计算的后端,懒惰是强大的。 Clojure在许多方面都很漂亮,而懒惰是其核心组成部分。 关于它以及它周围的大量文章。 它们涵盖了如何编写此类代码的各个方面。 大多数情况下,例如斐波那契(Fibonacci)或通用数字生成器。 随之会出现一些问题。 我肯定有一些-

  • 对于新手,来自其他编程风格的人来说,是否容易理解? 收养的障碍?
  • 如何将其与您和我正在研究的实际问题联系起来?
  • 采用和将其转移到“生产”的障碍是什么?
  • 有哪些陷阱和谬论?

因此,这是回答我懒惰时所学到的教训的一种方法。

什么是懒惰?

首先是“什么”。 懒惰,即懒惰的求值是代码的一部分,直到绝对需要它们的返回值时才进行求值。 在类似的思路上考虑一下,因为我们推迟了提交纳税申报表的时间,直到最后一次提交。 在此期间,我们去做实际上有用的事情是非常强大的。 我们可以尽早提起诉讼,但没有,也没有必要。 这绝对使我感到“高效”。 (旁注:有关双关语的更多信息,请查看以下参考资料中的帖子(:)

将其带回到后端,有些表达式直到流程中的后续阶段才需要完全求值。 在那个水平上是有意义的。 现在是“如何”。 在这一部分中,让我们仔细研究Clojure的核心原则。 懒惰的评估绝对是其中之一。 特别地,惰性是通过可以在函数之间传递的惰性序列构造的,并且仅当发出eval命令时才进行评估。 这是我的意思的简要说明(如https://clojuredocs.org/clojure.core/lazy-seq所示

;; The following defines a lazy-seq of all positive numbers.  Note that 
;; the lazy-seq allows us to make a recursive call in a safe way because
;; the call does not happen immediately but instead creates a closure.

user=> (defn positive-numbers
([] (positive-numbers 1))
([n] (lazy-seq (cons n (positive-numbers (inc n))))))
#'user/positive-numbers

user=> (take 5 (positive-numbers))
(1 2 3 4 5)

太棒了! 有了这,这是我的第一堂课

第1课-很棒,我想偷懒,但是我到底想偷懒做什么?

为什么懒惰?
来源-imgflip.com

“你为什么问。 总是有部分代码在不需要之前才需要完成。 即使他们这样做了,我们也不需要评估中的所有返回值,也许只是n(count allresults)这听起来似乎具有相当的优势。

让我们打破positive-numbers例子,不要懒惰,不涉及潜在的成本。

(defn not-lazy-positive-numbers [n]
(mapv
#(let [v (inc %)]
; to know when evaluation happens
(println "executing" v)
v)
(range (- n 1) (+ n 10))))
;returns n to n+10 values starting from n
(not-lazy-positive-numbers 10)
;executing 10
;executing 11
;executing 12
;executing 13
;executing 14
;executing 15
;executing 16
;executing 17
;executing 18
;executing 19
;executing 20
;[10 11 12 13 14 15 16 17 18 19 20]

not-lazy-positive-numbers立即求值。 为了说明这一点,假设我们必须从10到40之间的正数池中选择15个数字。

(take 15 (concat (not-lazy-positive-numbers 10) (not-lazy-positive-numbers 20) (not-lazy-positive-numbers 30)))
;executing 10
;executing 11
;executing 12
;executing 13
;executing 14
;executing 15
;executing 16
;executing 17
;executing 18
;executing 19
;executing 20
;executing 20
;executing 21
;executing 22
;executing 23
;executing 24
;executing 25
;executing 26
;executing 27
;executing 28
;executing 29
;executing 30
;executing 30
;executing 31
;executing 32
;executing 33
;executing 34
;executing 35
;executing 36
;executing 37
;executing 38
;executing 39
;executing 40
(10 11 12 13 14 15 16 17 18 19 20 20 21 22 23)

哇,即使我们只需要其中15个值,它也会评估所有值,即30+。

第2课-有很多代码节可以完成所需的工作,请查找它们

现在,从原始示例中应用一些懒惰,

(defn lazy-positive-numbers [n]
(println "executing" n) ; to know what's executing, returns a lazy seq of max 1+10 executions
(lazy-seq (cons n (take 10 (lazy-positive-numbers (inc n))))))
(lazy-positive-numbers 10)
;executing 10
;executing 11
;executing 12
;executing 13
;executing 14
;executing 15
;executing 16
;executing 17
;executing 18
;executing 19
;executing 20
;executing 21
;(10 11 12 13 14 15 16 17 18 19 20)

lazy-positive-numbers总是作为延迟序列返回10个值。 (旁注-惰性seq的难看代码,可能是线程持久的:/)

再说一次,我们必须从10到40之间的正数池中选择15个数字。

(take 15 (concat (lazy-positive-numbers 10) (lazy-positive-numbers 20) (lazy-positive-numbers 30)))
;executing 10
;executing 20
;executing 30
;executing 11
;executing 12
;executing 13
;executing 14
;executing 15
;executing 16
;executing 17
;executing 18
;executing 19
;executing 20
;executing 21
;executing 21
;executing 22
;executing 23
;executing 24
;(10 11 12 13 14 15 16 17 18 19 20 20 21 22 23)

太棒了! 使用惰性评估,执行次数减少到18(对于主要源的初始化为-3)。

第3课-懒惰会带来创建顶级初始化的开销。 更多的惰性源,更多的开销。

关于懒惰IRL

(不重要的课程-IRL =“现实生活中”)

现在让我们举一个真实的例子。 我们正在呈现一个包含帖子和更新的用户供稿页面。 返回的帖子数基于某些相关性参数并进行了分页。 这些帖子来自多个来源,以根据收到的不同信号构建实时提要。 就像一个业务流程。 来源可以来自数据库,ML数据模型,缓存,并回填了一些预设数据。 所有源都有检索数据的计算量和等待时间。 在伪代码中,它看起来应该像

posts = [];
until(posts.length >= limit;
sourcelist = get-data-sources()
data = get-data-from-sources()
remdata = data.slice(posts.length - limit - data.length)
posts.push(remdata))

看起来很懒惰。 Sourcelist中的每个条目都是一个生成器,即返回可以合并在一起,转换并添加到返回的帖子中的一系列数据。 应用惰性原则,所有方法都看起来像

(defn get-data-sources []
(lazy-seq
[#(lazy-positive-numbers 10) ;data generators, can be replaced with actual db calls
#(lazy-positive-numbers 20)
#(lazy-positive-numbers 30)]))
(defn get-data-from-sources [sourcelist]
(map #(apply % []) sourcelist))
(defn get-posts [limit]
(->>
(get-data-sources) ;returns seq of data-sources
(get-data-from-sources) ;returns a lazy-seq of results
(apply concat) ;concat all lazy-seq before taking
(take limit)))
;Executing should call other data sources only after exhausting the current one
(get-posts 15)
;executing 10
;executing 20
;executing 30
;executing 11
;executing 12
;executing 13
;executing 14
;executing 15
;executing 16
;executing 17
;executing 18
;executing 19
;executing 20
;executing 21
;executing 21
;executing 22
;executing 23
;executing 24
;(10 11 12 13 14 15 16 17 18 19 20 20 21 22 23)

hoo! 想象一下,执行40次调用以检索15个条目,。

第4课-懒惰有助于填补来自不同来源的序列。

另一个值得注意的重要事实是,每个动作都从伪代码分解为功能。 如果没有功能,使用懒惰将变得更加困难。

第5课-如果它不起作用,就很难偷懒。

懒惰有多快?

懒惰通常会使人感到低速。 但是是这样吗? 在输出中添加一个很小的工具

(defn eval-not-lazy []
(time
(let [result (take 15 (concat (not-lazy-positive-numbers 10) (not-lazy-positive-numbers 20) (not-lazy-positive-numbers 30)))]
(println result))))
(defn eval-lazy []
(time
(let [result (take 15 (concat (lazy-positive-numbers 10) (lazy-positive-numbers 20) (lazy-positive-numbers 30)))]
(println result))))
(eval-not-lazy)
;(10 11 12 13 14 15 16 17 18 19 20 20 21 22 23)
;"Elapsed time: 0.571285 msecs"
;nil
;(eval-lazy)
;(10 11 12 13 14 15 16 17 18 19 20 20 21 22 23)
;"Elapsed time: 0.415577 msecs"
;nil

绝对与非延迟版本的速度相同。

来源— imgflip.com

第6课-懒惰不会对速度产生负面影响,它可以相同或更好

并发和懒惰

现在,让我们看看惰性对并发执行有何反应以及是否可以使用并发。 在我们的示例中,使用pmap并行执行。 (Clojure的喜悦:))。

(defn parallel-get-data-from-sources [sourcelist]
(pmap #(apply % []) sourcelist))
(defn parallel-get-posts [limit]
(->>
(get-data-sources) ;returns seq of data-sources
(parallel-get-data-from-sources) ;returns a lazy-seq of results
(apply concat) ;concat all lazy-seq before taking
(take limit)))
(parallel-get-posts 15)
;executingexecuting 1020

;executing 30
;executing 11
;executing 12
;executing 13
;executing 14
;executing 15
;executing 16
;executing 17
;executing 18
;executing 19
;executing 20
;executing 21
;executing 21
;executing 22
;executing 23
;executing 24
;(10 11 12 13 14 15 16 17 18 19 20 20 21 22 23)

是的,可以使用它,但是没有意义,因为我们要填充一个序列,除非可以并行进行初始化。

第7课-并发不影响惰性,序列按顺序填充

Gotcha的

像那里的所有锤子一样,这是一把锤子,可服务于特定的指甲组,最好了解何时不应该使用它。 Clojure的许多核心功能(如takemaprepeat等)返回惰性序列。 在应评估整个表达式集的情况下,应将懒惰性短路以评估所有表达式。

在我们的例子中,短路可以与被触发doall

(->>
(concat
(lazy-positive-numbers 10)
(lazy-positive-numbers 20)
(lazy-positive-numbers 30))
(doall)
(take 15))
;executing 10
;executing 20
;executing 30
;executing 11
;executing 12
;executing 13
;executing 14
;executing 15
;executing 16
;executing 17
;executing 18
;executing 19
;executing 20
;executing 21
;executing 21
;executing 22
;executing 23
;executing 24
;executing 25
;executing 26
;executing 27
;executing 28
;executing 29
;executing 30
;executing 31
;executing 31
;executing 32
;executing 33
;executing 34
;executing 35
;executing 36
;executing 37
;executing 38
;executing 39
;executing 40
;executing 41
;(10 11 12 13 14 15 16 17 18 19 20 20 21 22 23)

如果我们错过doall所有expresssions将不进行评估。

第8课-警惕Clojure核心功能 map != mapv filter != filterv

第9课-使用 doall 缩短懒惰并评估所有

总之,“懒惰”,您真棒。

以上课程来自Swym Corporation的内部开发/工程演讲。 这次演讲对Clojure的功能模式进行了更广泛的概述,我希望在我的下一篇文章中介绍。

特别感谢SaumitraSupritha和Shivam帮助使这篇文章看起来不错。 希望您喜欢阅读它,就像我们喜欢一起阅读它一样。 请随意输入您的想法。 感谢您的意见和问题! :)

PS:也许最好的? 不,谢谢。 这是“希望”所采取的。 我不是那个意思。 安迪·杜弗雷斯(Andy Dufresne)做过:)

参考文献
  1. https://clojuredocs.org/clojure.core/lazy-seq-有关如何编写“惰性生产者”的大量示例
  2. https://clojure.org/reference/lazy
  3. http://clojure-doc.org/articles/language/laziness.html
  4. https://medium.com/lazy-eval/lazy-seq-in-clojure-da06f6d35971
  5. https://stuartsierra.com/2015/08/25/clojure-donts-lazy-effects
  6. https://github.com/danielmiladinov/joy-of-clojure/blob/master/src/joy-of-clojure/chapter6/laziness.clj
  7. https://noobtuts.com/clojure/being-lazy-in-clojure
  8. https://practicalli.github.io/clojure/thinking-functionally/lazy-evaluation.html
  9. http://www.thesoftwaresimpleton.com/blog/2014/09/08/lazy-seq/

From: https://hackernoon.com/clojure-lessons-from-laziness-252ca7fc4fa7

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值