The Joy of Clojure – Laziness(6.3)

2023-11-02 08:30
文章标签 clojure 6.3 joy laziness

本文主要是介绍The Joy of Clojure – Laziness(6.3),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Clojure is partially a lazy language.

Clojure is lazy in the way it handles its sequence types.

Laziness是FP重要的特征, pure FP应该是lazy language, 比如Haskell. 当然完全的lazy带来问题是, 无法保证程序的执行顺序. 
而Clojure是在实用性和pure FP之间做妥协的, 所以他只是部分的支持lazy, 主要就表现在处理sequence的时候, 在其他大部分的时候仍然是eager(not lazy)

比如, 典型的例子, 函数参数, 
(- 13 (+ 2 2)) 
对于eager的做法是, 先算出2+2=4, 然后将4传入 
而lazy的做法是, 直接将(+ 2 2)传入, 至到减法时, 必须要算的时候, 才会算出4

 

既然Clojure只有在处理sequence时, 才是lazy的, 具体来看看clojure的lazy-seq

Understanding the lazy-seq recipe

先来个例子看看, 为什么需要lazy-seq?

 

(defn rec-step [[x & xs]] ;第一个item传入x,剩下的传入xs
  (if x
    [x (rec-step xs)]
    []))


(rec-step [1 2 3 4])
;=> [1 [2 [3 [4 []]]]]

(rec-step (range 200000))
;=> java.lang.StackOverflowError


会blow stack, 因为使用递归函数, 所以要解决这个问题, 要不使用tail optimization, 但这儿使用lazy-seq更合适一些

怎样使用lazy-seq, 有如下recipe...

lazy-seq recipe for applying laziness to your own functions:

1. Use the lazy-seq macro at the outermost level of your lazy sequence producing expression(s). 
2. If you happen to be consuming another sequence during your operations, then use ‘rest’ instead of ‘next’. 因为next比rest多一次realize, 因为next要先判断是否为空 
3. Prefer higher-order functions when processing sequences. 
4. Don’t hold onto your head.

根据这个recipe, lazy version实现如下, 其实只是简单的加上lazy-seq封装(first, rest其实等同于上面的写法)

 

(defn lz-rec-step [s]
  (lazy-seq
    (if (seq s)
    [(first s) (lz-rec-step (rest s))]
    [])))


更简单的形式如下,

 

(defn simple-range [i limit]
  (lazy-seq
    (when (< i limit)
    (cons i (simple-range (inc i) limit)))))

 

(simple-range 0 9)
;=> (0 1 2 3 4 5 6 7 8)


(simple-range 0 200000) 是否会blew stack?

不会, 原因是没有真正使用递归 
你可以试想, 如果直接使用递归, lazy是没有意义的, 递归需要从底至上的回溯得到值 
所以虽然这儿加上lazy-seq, 形式上仍然好像使用了函数递归调用, 其实lazy-seq这儿做了优化.

 

那么lazy-seq做了怎样的优化?

image

Each step of a lazy seq may be in one of two states.  
Unrealized state, it’ll contain a function or closure of no arguments (a thunk) that can be called later to realize the step. 
Realized state, the thunk’s return value is cached instead, and the thunk itself is released 
Note that although not shown here, a realized lazy seq may simply contain nothing at all, called nil, indicating the end of the seq.

比较清晰的可以看出lazy-seq是怎么实现的 
刚开始所有items都是unrealized, 其实我的理解就是一个闭包(simple-range 0 9) 
每调用一次first, 就会做一次realize, 并产生一个realized item, 如图, 调用两次first产生两个realized item, 0, 1, 而rest得到剩下的unrealized items(simple-range 2 9) 
可以看出, 完全没有使用到stack, 所以不会出现blow stack的现象. 
需要注意的是, realized item只是被cache在memory里面, 如果没有被referenced, 会自动被垃圾回收掉, 所以realized lazy seq是不会包含任何结果的, nil, 用于指向seq尾部. 
所以对于很大的seq, 不要hold head, 即不要refer realized item, 这样会导致无法被自动回收, 导致outofmemory

 

对于lose head, 可以看下面的例子, 只是(first r) (last r)的顺序不同, 就会导致outofmemory 

 

(let [r (range 1e9)] [(first r) (last r)])
;=> [0 999999999]
(let [r (range 1e9)] [(last r) (first r)])
; java.lang.OutOfMemoryError: GC overhead limit exceeded

原因就是第二个例子, 需要一直hold主first item, 所以导致JVM无法进行GC, 所以OutOfMemoryError 
部分由于clojure不是pure FP, 必须严格按照顺序执行, 对于Haskell这样的pure FP, 可以通过简单的complier优化解决这个问题 

这个例子如果不够清晰, 看这个blog, 配图的, 很清楚. Clojure惰性序列的头保持问题

 

The delay and force macros

Although Clojure sequences are largely lazy, Clojure itself isn’t. 
In most cases, expressions in Clojure are evaluated once prior to their being passed into a function rather than at the time of need. But Clojure does provide mechanisms for implementing what are known as call-by-need semantics. The most obvious of these mechanisms is itsmacro facilities, but we’ll defer that discussion until chapter 8.

The other mechanism for providing what we’ll call explicit laziness are Clojure’s delay and force
In short, the delay macro is used to defer the evaluation of an expression until explicitly forced using the force function.

Clojure本身不是lazy语言, 但他也支持这种call by need的语义. 简单的方式就是通过delay, force

例子,

 

(defn inf-triangles [n]  ;函数返回lazy linked-list, head存值, tail存产生下个item的闭包
  {:head (triangle n)    ;triangle表示计算逻辑
   :tail (delay (inf-triangles (inc n)))}) ;通过delay, 不会实际调用函数

 

(defn head [list] (:head list))
(defn tail [list] (force (:tail list))) ;通过force, 强制delay的闭包执行

 

;定义tri-nums, 这种lazy structure叫 ‘head strict’, 不同于lazy-seq, 因为head的值会先被realize, 而lazy-seq不会realize, 必须显示调用first

 

(def tri-nums (inf-triangles 1))
(head tri-nums)
;=> 1
(head (tail tri-nums))
;=> 3
(head (tail (tail tri-nums)))
;=> 6

image

 

Of course, writing programs using delay and force is an onerous way to go about the problem of laziness, and you’d bebetter served by using Clojure’s lazy sequences to full effect rather than building your own from these basic blocks.


本文章摘自博客园,原文发布日期:2013-02-17

这篇关于The Joy of Clojure – Laziness(6.3)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/329372

相关文章

6.3中值滤波

目录 实验原理 示例代码1 运行结果1 示例代码2 运行结果2 实验原理 中值滤波(Median Filtering)是一种非线性滤波技术,常用于图像处理中去除噪声,特别是在保留边缘的同时减少椒盐噪声(salt-and-pepper noise)。OpenCV中的cv::medianBlur函数可以实现中值滤波。 函数原型 void medianBlur( InputAr

Joy-Trade智能量化平台发起“日星悦易”的抽奖活动

Joy-Trade(悦交易)智能量化平台将于11月11日上午11点正式启动“日星悦易”的抽奖活动,通过星座文化为使投资者加深对投资运势把握,提升投资者的交易心态,活动将作为Joy-Trade量化交易的文化宣导。悦交易联席CEO Rocket表示:“星座在历史中是象征着一个人的性格和财运的具象化,很多人在投资的过程中总是过于焦虑,通过星座文化的活动我们想在这样紧张的氛围中给加密市场的投资者带来一些轻

Java 6.3 - 定时任务

为什么需要定时任务? 常见业务场景: 1、支付10min失效 2、某系统凌晨进行数据备份 3、视频网站定时发布视频 4、媒体聚合平台每10min抓取某网站数据为己用 …… 这些场景需要我们在某个特定时间去做某些事情。 单机定时任务技术有哪些? Timer java.util.Timer 是 JDK 1.3 开始就支持的一种定时任务的实现方式。 Timer 内部使用了一个

Embarcadero Dev-C++ 6.3安装教程

1. 准备软件安装包 C++课程所需的Embarcadero Dev-C++ 6.3软件安装包如下图所示: 软件安装包已经上传到百度网盘和阿里云盘,下载链接分别为: ①百度网盘链接: https://pan.baidu.com/s/1a8BbUKrWHljbbEb5DtqxCw?pwd=hgdk ②阿里云盘链接: https://www.alipan.com/s/QyiuPhCmvKq

数据结构(6.3_2)——图的深度优先遍历

树的深度优先遍历  树的深度优先遍历分为先根遍历和后根遍历。 图的深度优先遍历 代码(只能遍历连通图)   //深度优先遍历void DFS(Graph G, int v) {//从顶点v出发,深度优先遍历图Gvisit(v);//访问初始顶点vvisited[v] = true;//对v做已访问标记for (w = FirsitNeighbor(G, v); w >= 0;

CentOS 6.3下安装jdk并配置环境变量

首先我是在VMware10上装的CentOS6.3 要想在CentOS6.3上安装jdk,首先到jdk官网上去下载一个linux版本的jdk,网址是http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html 我下的是jdk-7u25-linux-x64.tar.gz,下下来之后把他复制到

在 Clojure 中,如何实现高效的并发编程以处理大规模数据处理任务?

在Clojure中,可以使用以下几种方式来实现高效的并发编程以处理大规模数据处理任务: 并发集合(Concurrent Collections):Clojure提供了一些并发集合数据结构,如ref、agent和atom,它们能够在多个线程之间共享和修改数据。通过使用这些数据结构,可以实现高效的并发访问和更新数据。 异步编程:Clojure提供了一些异步编程的机制,如promise和futur

modbus poll 6.3.1破解版 附注册码

http://www.ddooo.com/softdown/70167.htm 1、下载解压缩,根据需求选择安装32位或64位版本; 2、成功安装后,点击菜单Connection–>Connect,将注册码粘贴到里面即可。 3、序列号:5A5742575C5D10 4、打开软件,modbus poll已经可以无限制免费使用。

CentOSnbsp;6.3安装OpenOffice

终端依次输入: (1)sudo yum install openoffice.org-writer (2) sudo  yum install openoffice.org-calc (3) sudo  yum install openoffice.org-draw openoffice.org-impress

在CentOS 6.3中安装与配置JDK-7

系统环境:centos-6.3 安装方式:rpm安装 软件:jdk-7-linux-x64.rpm 下载地址:http://www.oracle.com/technetwork/java/javase/downloads/java-se-jdk-7-download-432154.html 检验系统原版本 [root@zck ~]# java -version java version "1.6