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

python打卡day49

知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...

数据链路层的主要功能是什么

数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...

Spring Boot面试题精选汇总

🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

大数据学习(132)-HIve数据分析

​​​​🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言&#x1f4…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...

python爬虫——气象数据爬取

一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...

适应性Java用于现代 API:REST、GraphQL 和事件驱动

在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...