当前位置: 首页 > 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:# 全局配…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)

在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马&#xff08;服务器方面的&#xff09;的原理&#xff0c;连接&#xff0c;以及各种木马及连接工具的分享 文件木马&#xff1a;https://w…...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...