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

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

【机器视觉】单目测距——运动结构恢复

ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛&#xf…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用

文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...

Web后端基础(基础知识)

BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...

苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会

在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...

手机平板能效生态设计指令EU 2023/1670标准解读

手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...

flow_controllers

关键点: 流控制器类型: 同步(Sync):发布操作会阻塞,直到数据被确认发送。异步(Async):发布操作非阻塞,数据发送由后台线程处理。纯同步(PureSync…...

Android屏幕刷新率与FPS(Frames Per Second) 120hz

Android屏幕刷新率与FPS(Frames Per Second) 120hz 屏幕刷新率是屏幕每秒钟刷新显示内容的次数,单位是赫兹(Hz)。 60Hz 屏幕:每秒刷新 60 次,每次刷新间隔约 16.67ms 90Hz 屏幕:每秒刷新 90 次,…...

RLHF vs RLVR:对齐学习中的两种强化方式详解

在语言模型对齐(alignment)中,强化学习(RL)是一种重要的策略。而其中两种典型形式——RLHF(Reinforcement Learning with Human Feedback) 与 RLVR(Reinforcement Learning with Ver…...