当前位置: 首页 > news >正文

通过Clojure中的集合与序列谈谈抽象的重要

与君共勉:生命不息,学习不止,切忌浮躁,静下心来,每天进步一点点。

Clojure简介

Clojure是一门运行在JVM上面的Lisp方言,其它的Lisp方言还有Scheme、Common Lisp等。Lisp相关的著名书籍有《计算机程序的构造和解释》(简称SICP)、《The Little Schemer》、《黑客与画家》。
Clojure可以和Java代码互操作,它会编译成字节码运行在JVM上面,所有的Java生态都可以为Clojure所用。
Clojure的语法比较怪异,采用前缀表达式,比如一般编程语言中的3+4、5-3、add(4,6),在Clojure中统一写成(+ 3 4)、(- 5 3)、(add 4 6),对编译器很友好(如果你了解抽象语法树的形式会很好理解),且因为这种统一,无需记忆所谓的操作符优先级,而且语法元素也很少,正是因为这种简洁,Lisp系的语言的数据和代码被统一了起来,即所谓的代码即数据(代码本身就是它的数据结构,比如(+ 3 4)是代码,但是它作为读取器处理的对象,可以将它看作list类型的数据,因为list类型的数据就是(1 2 3)或(+ 4 5)这种形式)。

普遍存在的数据结构

大部分编程语言都有集合类型,比如array、list、set、map等等,它们都有自己的具体实现。

比如在静态类型语言中,如Java、Rust,数组的元素都是连在一起的,数组的元素类型都是一致的,这样根据下标访问数组元素,可以简单地通过数组指针加上数组元素占用的字节大小乘以下标快速定位到特定元素。动态语言中,如JavaScript中,数组的元素类型不要求是一致的,比如可以这样声明一个数组:

let arr = [2,"花无缺"]; // 数组元素可以是Number和String类型混在一起的

比如list,在Java中分ArrayList和LinkedList,LinkedList是通过链表来实现的,Clojure中的list也是通过链表来实现的(链表元素在内存中可能相隔很远),链表实现的优点是,访问链表头特别快,在链表头追加一个元素特别快,但是访问链表中其它的元素比较慢,需要一个一个地跳转。

Clojure中的集合类

Clojure中常用的集合类型的数据结构有:list、vector、set、map等,它们有字面量表示方式(即在代码中写死的表示),也可以通过函数创建它们。

例如:

;;list的字面量需要有一个单引号在小括号前面,如果不加,Clojure会认为(1 2 3)中的1是一个函数调用,从而报错
(def this-is-list '(1 2 3)) ;;将包含1、2、3这三个数字的list绑定到this-is-list变量上 
(def this-is-list2 (list 1 3 4)) ;;通过list函数创建一个list;;vector的字面量是中括号包起来的
(def this-is-vector ["江苏" "南京" "江宁"]) ;;通过字面量创建一个vector,其中元素是三个字符串
(def this-is-vector2 (vector :name :sex :age)) ;;通过vector函数创建一个vector,其中元素是三个关键字(keyword);;set的字面量要用#{}包起来
(def this-is-set #{2 3 4}) ;;字面量方式
(def this-is-set2 (set [4 5 6]));;通过set函数创建,注意set函数后面的参数必须是集合类型,这儿用一个vector作为参数
(def this-is-set3 (set '(4 5 6)));;通过set函数创建,注意set函数后面的参数必须是集合类型,这儿用一个list作为参数;;map的字面量要用大括号{}包起来,Clojure中的map的各个key-value之间可以不用逗号分割
(def this-is-map {:name "明月" :address "无双城"}) ;;字面量方式创建map,key的类型是关键字(keyword),value的类型是字符串
(def this-is-map2 (hash-map :name "第二梦" :address "绝情谷"));;通过hash-map函数创建map

Clojure的哲学

Clojure是一门特别推崇函数式编程范式的编程语言,它有一个设计哲学:

It is better to have 100 functions operate on one data structure 
than 10 functions on 10 data structures.
—Alan Perlis 

在1种数据结构上定义100中操作方法,比在10种数据结构上定义10种方法更好。

也就是说,Clojure编程语言,不像面向对象编程语言那样提倡多创建自己的类型,然后再在每个类型上添加方法,通过方法之间的调用来构造复杂的系统,比如在Java中,你可以自定义User类、Employee类,每个类都有自己的方法。然而,这些自定义类的字段仍不可免俗地是String、Integer、Boolean这些类型。所以,Clojure的设计者认为,提供map类型的数据结构,里面可以放各种类型,然后通过给map提供丰富多彩的函数操作,一样可以构造复杂系统。

比如在Java中定义的User类可能是这样的:

public class User {private String name;// 通过给User类定义name方法来访问name字段public String getName(){return this.name;}private int age;// 通过给User类定义getAge方法来访问age字段public int getAge(){return this.age;}
}

在Clojure中,我们可以将用户信息放到map中,然后通过map的key来访问用户信息:

(def user {:name "江别鹤" :age 50})
;; 通过 :name字段来获取姓名
(user :name)
;; 或者通过 :name关键字作为函数来使用,没错,Clojure中的关键字可以作为函数来使用
(:name user)
;; => "江别鹤"
;; 通过 :age字段来获取年龄
(user :age)
;; 或者通过 :age关键字作为函数来使用
(:age user)
;; => 50

上面我们介绍过Clojure中的几种常见的集合类型,在Clojure的设计哲学的指导下,Clojure中的集合类型上面有大量的函数可以使用,比如:

;; 提到的函数有点多,耐心点,为了后面讲的抽象做铺垫
;; empty?函数,没错,Clojure中的函数或变量中可以包含问号、中划线、感叹号等
(empty? [1 3 4]) ;; 判断vector是否为空
(empty? '("慕容仙" "江玉凤" "苏樱")) ;; 判断list是否为空
(empty? #{23 45 "呵呵"}) ;; 判断vector是否为空
(empty? {:name "花无缺" :age 20}) ;; 判断map是否为空;; seq函数,返回序列
(seq [23 4 5]) ;; 返回(23 4 5)
(seq '(43 5 66)) ;; 返回(43 5 66)
(seq #{45 23 2}) ;; 返回(2 23 45)
(seq {:name "怜星" :sister "邀月"}) ;; 返回([:name "怜星"] [:sister "邀月"]);; first函数
(first [23 4 5]) ;; 获取vector的第一个元素,返回23
(first '(43 5 66)) ;; 获取list的第一个元素,返回43
(first #{45 23 2}) ;; 获取set的第一个元素,返回2
(first {:name "怜星" :sister "邀月"}) ;; 获取map的第一个key-value对,返回[:name "怜星"];; rest函数,获取集合第一个元素之外的元素列表
(rest [23 4 5]) ;; 获取vector的第一个元素之外的元素,(4 5)
(rest '(43 5 66)) ;; 获取list的第一个元素之外的元素,返回(5 66)
(rest #{45 23 2}) ;; 获取set的第一个元素之外的元素,返回(23 45)
(rest {:name "怜星" :sister "邀月"}) ;; 获取map的第一个key-value对之外的元素,返回 ([:sister "邀月"]);; map函数,类似Java中stream上面的map函数,对每一个元素应用一次函数
(defn say-hello [x] (str "Hello," x)) ;; 定义一个say-hello函数,对入参拼上一个"Hello,"字符串前缀
(map say-hello ["南京" "兰州"]) ;; 返回("Hello,南京" "Hello,兰州")
(map say-hello #{"江宁" "百家湖"}) ;; 返回("Hello,江宁" "Hello,百家湖");; reduce函数,在Clojure中+-*/这些符号都是函数
(reduce + [2 3 4]) ;; 对vector中的所有元素求和,返回9
(reduce * '(4 6 7)) ;; 对list中的所有元素求乘积,返回168;; conj函数,往集合中追加元素
(conj [2 3 4] 5 6 7) ;; 返回 [2 3 4 5 6 7]
(conj [1 2] 3) ;; 返回[1 2 3]
(conj '(1 2 3) 4) ;; Clojure中的list底层实现为链表,追加元素的时候,放在表头最快,所以返回(4 1 2 3)
(conj '(1 2 3) 4 5) ;; Clojure中的list底层实现为链表,追加元素的时候,放在表头最快,返回(5 4 1 2 3)
(conj {:name "张无忌" :age 25} [:first-girl-friend "周芷若"]) ;; 返回{:name "张无忌", :age 25, :first-girl-friend "周芷若"};; cons函数,往序列中添加元素
(cons 3 [1 2 3]) ;; 返回(3 1 2 3)
(cons 5 '(2 3)) ;; 返回(5 2 3)
(cons 6 #{5 6}) ;; 返回(6 6 5)

介绍这么多函数,有的函数返回的是集合本来的类型。比如:

对于conj函数。入参是vector,返回的还是vector,入参是list,返回的还是list。

有的函数返回的和入参的集合类型不同,比如:

cons函数不管入参是什么类型,返回的都是小括号包起来的输出。

是否有点凌乱?

抽象的力量

让我们站在更高的角度来审视这个问题。我们知道对于Java的List、Set、Map,一些工具类,如CollectionUtils提供了isEmpty、isNotEmpty来判断集合是否为空。对于List、Set、Map中的元素想挨个处理,可以通过这些集合上的stream方法转换成流以后,使用map、filter等方法来处理。

也就是说,我们通过提供更高层次的抽象,来屏蔽掉了底层数据结构的不同,让上层可以用统一的方式来处理底层的数据。

所谓的抽象,不过是一组操作的集合,满足了这些操作的集合的数据类型,就是这种抽象的一种具体实现。比如汽车是一种抽象,它有前进、转弯、后退、加油、开窗等操作,具体的类型可以是大巴车、小轿车、公交车。

然而,对一组类型进行抽象,只能提取它们共同部分的信息,比如集合都有获取第一个元素、获取第n个元素、遍历元素这些操作,所以这些操作都可以提取成一种更高层次的抽象,在Clojure中表现为first、nth、map这些函数,然而真正落实到具体的数据结构上的时候,它们又不同,所以需要将它们转换成中间的一层统一的抽象,在Clojure中就是所谓的seq(或者叫sequence,即序列),当各种各样的数据结构转换成seq,就可以对seq进行first、second、rest、map等操作。就像Java中的各种集合调用了stream之后转换成了统一的抽象后,才可以使用map、filter、collect等操作。

Clojure中的两大抽象

Clojure有两个重要的抽象,collection和seq,即集合和序列,Clojure的list、vector、map、set都实现了这两大抽象。前面我们说过,所谓的抽象就是一组操作的集合,符合某一个抽象的类型必定都具备这个抽象中的所有操作,而满足一个抽象中的所有操作的类型也可以实现为这个抽象,进而可以被针对这个抽象实现的所有的函数使用。

collection的抽象中的主要操作有empty?、contains?、every等,它是将数据作为一个整体进行处理的;

seq的抽象中的主要操作有first、second、rest、map等,它是将数据作为一个序列,从而可以一个一个地处理。

当我们将list、vector、set、map数据类型传递到形参是seq类型的函数式,会发生隐式转换,会先调用seq函数将其转换成seq抽象,比如:

;; 下面两个是等价的,返回的都是4,因为(seq #{2 3 4})返回的是(4 3 2)这个seq
(first #{2 3 4})
(first (seq #{2 3 4}))

参考资料
1.《Clojure for the brave and true》

相关文章:

通过Clojure中的集合与序列谈谈抽象的重要

与君共勉:生命不息,学习不止,切忌浮躁,静下心来,每天进步一点点。 Clojure简介 Clojure是一门运行在JVM上面的Lisp方言,其它的Lisp方言还有Scheme、Common Lisp等。Lisp相关的著名书籍有《计算机程序的构…...

Rust---模式(Pattern)匹配

目录 模式是什么它用来做什么模式匹配和赋值为什么会有模式匹配模式匹配用在什么地方match 表达式if let表达式while let表达式for 循环let 语句函数参数不可驳模式匹配和可驳模式匹配模式是什么 在Rust中,模式(Pattern)是一种用于匹配和解构数据的语法结构。模式匹配中常用…...

MATLAB 计算点投影到平面上的坐标(59)

MATLAB 计算点投影到平面上的坐标(59) 一、算法介绍二、算法实现1.代码2.结果一、算法介绍 点投影到平面,计算投影点的坐标,下面提供MATLAB版本的计算程序,直接运行即可,内有验证数据,具体看代码即可。 二、算法实现 1.代码 代码如下(示例): % 平面上的三个点分…...

2024年MathorCup数学建模B题甲骨文智能识别中原始拓片单字自动分割与识别研究解题文档与程序

2024年第十四届MathorCup高校数学建模挑战赛 B题 甲骨文智能识别中原始拓片单字自动分割与识别研究 原题再现: 甲骨文是我国目前已知的最早成熟的文字系统,它是一种刻在龟甲或兽骨上的古老文字。甲骨文具有极其重要的研究价值,不仅对中国文…...

嵌入式与移动物联网开发教程和案例

一、嵌入式与移动物联网概述 嵌入式系统是指嵌入到设备中的专用计算机系统,用于控制、监视或辅助设备操作。而移动物联网则是指通过物联网技术将各种智能设备与互联网连接起来,实现设备之间的互联互通和智能化管理。嵌入式与移动物联网技术的结合&#…...

AttachVoExample

目录 1、 AttachVoExample 1.1、 GeneratedCriteria 1.2、 addCriterion 1.3、 andFnameGreaterThanOrEqualTo 1.4、 GeneratedCriteria Atta...

图像处理特征提取

图像处理中的特征提取是指从图像数据中提取出具有区分性和代表性的特征,以用于图像分类、目标检测、图像匹配等任务。下面介绍几种常见的图像处理特征提取方法: 颜色特征:颜色是图像中最直观且重要的特征之一。常见的颜色特征提取方法包括颜色…...

前端大屏适配几种方案

一、方案一:remfont-size 动态设置HTML根字体大小和body字体大小,会使用到lib-flexible.js插件lib-flexible.js (function flexible(window, document) {var docEl document.documentElementvar dpr window.devicePixelRatio || 1// adjust body font…...

2011年认证杯SPSSPRO杯数学建模B题(第一阶段)生物多样性的评估全过程文档及程序

2011年认证杯SPSSPRO杯数学建模 B题 生物多样性的评估 原题再现: 2010 年是联合国大会确定的国际生物多样性年。保护地球上的生物多样性已经越来越被人类社会所关注,相关的大规模科研和考察计划也层出不穷。为了更好地建立国际交流与专家间的合作&…...

AcWing 793. 高精度乘法——算法基础课题解

AcWing 793. 高精度乘法 题目描述 给定两个非负整数(不含前导 00) A 和 B,请你计算 AB 的值。 输入格式 共两行,第一行包含整数 A,第二行包含整数 B。 输出格式 共一行,包含 AB 的值。 数据范围 1≤…...

【一刷《剑指Offer》】面试题 3:二维数组中的查找

力扣对应题目链接:240. 搜索二维矩阵 II - 力扣(LeetCode) 核心考点:数组相关,特性观察,时间复杂度把握。 一、《剑指Offer》对应内容 二、分析题目 正常查找的过程本质就是排除的过程,谁排除…...

Linux下静态库与动态库使用总结

区别 使用静态库占用的磁盘空间相对比动态库要大。 如果多个可执行程序使用库中同一个函数,那么链接静态库时同一个函数的代码会被复制多份,而链接动态库只复制一份。动态库可共享且版本更新方便 静态链接库在程序编译的时候就被加载进来,不…...

分布式任务调度:架构、原理与实践

引言 在当今快速发展的科技领域中,任务调度作为管理和优化计算资源的重要工具,扮演着至关重要的角色。从单机环境到分布式系统,任务调度的演进不仅跟随着计算机技术的进步,更是为了应对日益复杂的应用场景和需求。本博客将深入探…...

ping命令返回无法访问目标主机和请求超时浅析

在日常经常用ping命令测试网络是否通信正常,使用ping命令时也经常会遇到这两种情况,那么表示网络出现了问题。 1、请求超时的原因 可以看到“请求超时”没有收到任何回复。要知道,IP数据报是有生存时间的,当其生存时间为零时就会…...

地球上的七大洲介绍

地球上的七大洲示意图: 1. 亚洲(Asia):世界上最大的洲,面积约为44579000平方公里。亚洲地域辽阔,包括从北极圈到赤道的各种气候和地形。它拥有世界上最多的人口,也是世界上一些最古老文明的发源…...

IntelliJ IDEA 2024 for Mac/Win:引领Java开发新纪元的高效集成环境

在日新月异的软件开发领域,一款高效、智能的集成开发环境(IDE)无疑是程序员们不可或缺的神兵利器。今天,我要为大家介绍的,正是这样一款集大成之作——IntelliJ IDEA 2024。无论是Mac用户还是Windows用户,只…...

Java 中命令模式,请用代码具体举例

在Java中,命令模式是一种行为设计模式,它允许将请求封装成一个对象,从而使得可以参数化其他对象对请求进行调用、队列化请求、或者记录请求日志,同时支持可撤销的操作。 下面是一个简单的示例代码,展示了如何使用命令模…...

低延时+高并发+强事务丨DolphinDB 交易型内存存储引擎 IMOLTP 使用指南

1. 背景 在一些数据库应用场景中,例如金融行业的交易系统,其主要工作负载来源于对关系表的高频度、高并发的更新和查询操作。这样的应用场景要求数据的读写和计算能够具有低延迟、高并发的特征,同时保证极高的数据一致性,并提供 …...

写代码的修养

看山是山,看水是水 此境界 对业务的思考是浅层的,代码写的不通用,扩展性差,表现在无设计模式 看山不是山,看水不是水 此境界 对业务的思考是中层的,代码写的通用,扩展性好,表现为…...

springboot 问题整合

springboot 启动后访问报错 问题:org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): 原因:mybatis 的全局配置文件和 sql 映射文件没有写 解决:在 application.yml 中添加 mybatis 配置 mybatis:# 全局配…...

UNIAPP二维码展示页亮度调至最亮返回恢复进入前亮度

onLoad(params) {let num plus.screen.getBrightness().toString(); //转字符串是要存到stoage中number类型会存储失败plus.storage.setItem("pmld", num)plus.screen.setBrightness(1); //设置屏幕亮度,范围0-1 }onUnload() {let platformuni.getSystem…...

Golang ProtoBuf 初学者完整教程:安装

一、Protobuf 特点 更高效:使用二进制编码,相比XML/JSON更加高效 跨语言支持:Protobuf 在 .proto 定义需要处理的结构化数据,可以通过 protoc 工具,将 .proto 文件转换为 C、C、Golang、Java、Python 等多种语言的代…...

Isolation Forest 简介

1. 简介 孤立森林 iForest(Isolation Forest)是一种无监督学习算法,用于识别异常值。其基本原理是:异常数据由于数量较少且与正常数据差异较大,因此在被隔离时需要较少的步骤。 两个假设: 1. 异常的值是非常少的(如果异常值很多&…...

Java爬虫携带sign签名

站点&#xff1a;https://www.mytokencap.com/ 代码分析先不写了&#xff0c;大家自行解决&#xff0c;贴代码 1、业务请求设计 public static void md5Pro() {String url "https://api.mytokenapi.com/ticker/currencylistforall";Map<String, String> he…...

设计者模式之中介者模式(下)

3&#xff09;中介者与同事类的扩展 1.结构图 新增了具体同事类Label和具体中介者类SubConcreteMediator。 2.代码实现 //文本标签类&#xff1a;具体同事类 public class Label extends Component {public void update() {System.out.println("文本标签内容改变&#…...

SAP SD学习笔记04 - 出荷Plant(交货工厂),出荷Point(装运点),输送计划,品目的可用性检查,一括纳入/分割纳入,仓库管理

上一章讲了SD的主数据。 SAP SD学习笔记03 - SD模块中的主数据-CSDN博客 本章讲出荷Plant&#xff08;交货工厂&#xff09;&#xff0c;出荷Point&#xff08;装运点&#xff09;和出和路线。 还是偏理论多一些&#xff0c;后面的文章尽量多加些练习巩固一下。 1&#xff0…...

bind包装器——C++新特性(三)

文章目录 bindbind函数模板的原型bind 包装器的用途其他使用示例 &#x1f396; 博主的CSDN主页&#xff1a;Ryan.Alaskan Malamute &#x1f4dc; 博主的代码仓库主页 [ Gitee ]&#xff1a;ryanala [GitHub]&#xff1a; Ryan-Ala bind bind也是一种函数包装器&#xf…...

MXNet的下载安装及问题处理

1、MXNet介绍&#xff1a; MXNet是一个开源的深度学习框架&#xff0c;以其灵活性和效率著称&#xff0c;支持多种编程接口&#xff0c;包括Python、C、R、Julia、Scala等。MXNet支持大规模分布式训练&#xff0c;同时兼顾CPU和GPU的计算资源&#xff0c;尤其擅长于模型并行和数…...

Python 中的列表排序和排序规则

Python 中的列表排序和排序规则 在 Python 中&#xff0c;列表的排序是一个常见的操作&#xff0c;可以使用内置函数 sorted() 或列表对象的 sort() 方法来完成。下面将介绍这两种方法以及排序规则的使用方式。 1. 使用 sorted() 函数排序列表&#xff08;临时性排序&#xf…...

面经整理1

感觉好几个都是backtracking Letter Combinations of a Phone Number - LeetCode 典型的backtracking&#xff0c;注意String的处理 class Solution {String[] keyboard new String[]{"", "", "abc","def","ghi","…...

中国疫情即将放开/外贸网站seo推广教程

遇到的几个问题&#xff1a; 1、android-ndk-r8d/build/core/build-binary.mk:41:***target file clean has both : and :: entries. Stop 解决办法&#xff1a;因为在libavfilter目录中的Makefile的末尾处多了Clean这个玩意儿将其注释掉或者删掉就可以了 2、parseutils.c文件多…...

游戏网站的导航条怎么做的/营销推广活动策划方案大全

本文译自PCL官网教程,原文链接如下 Implicit Shape Model 隐式形状模型 在这个教程中我们将学习如何使用在 pcl::ism::ImplicitShapeModel 类中实现的隐式形状模型算法.这个算法在Jan Knopp, Mukta Prasad, Geert Willems, Radu Timofte, and Luc Van Gool撰写的文章”Hough…...

网站描述代码/怎么做app推广

福利资料见文末&#xff01; 监控redis通过info信息来监控redis的状态 自动发现脚本编写 cat /etc/zabbix/scripts/redis_parameter #自动发现redis脚本参数的脚本 #!/bin/bash /usr/local/bin/redis-cli -p 3000 -a redis66cs "info">/home/zabbix/tmp/info.tx…...

山西网站设计/快速排名生客seo

对于中断通俗点说&#xff1a;就是让单片机的cpu暂停执行当前任务转去执行引起中断的任务。刚开始学习单片机时非常排斥中断方面的东西因为要记忆很多寄存器相关的东西什么IE&#xff0c;IP等但是仔细一想如果不懂中断就只能写最简单的顺序执行程序而且非常浪费单片机的这些中断…...

wordpress本地添加图片不显示图片/站内推广有哪些具体方式

本地方法栈 Java虚拟机栈于管理Java方法的调用&#xff0c;而本地方法栈用于管理本地方法的调用。 本地方法栈&#xff0c;也是线程私有的。 允许被实现成固定或者是可动态扩展的内存大小。&#xff08;在内存溢出方面是相同的&#xff09; 如果线程请求分配的栈容量超过本…...

个人网站有必要备案吗/伟哥seo博客

<marquee>滚动标签 <marquee>标签&#xff0c;它是成对出现的标签&#xff0c;首标签<marquee>和尾标签</marquee>之间的内容就是滚动内容。<marquee>标签的属性主要有behavior、bgcolor、direction、width、height、hspace、vspace、loop、scro…...