抖音技术能力大揭密!钜惠大礼、深度体验,尽在火山引擎增长沙龙,就等你来! 立即报名>> 了解详情
写点什么

是什么让 Clojure 脱颖而出?

2019 年 8 月 29 日

是什么让 Clojure 脱颖而出?

本文最初发布于 On stranger tides 博客,经原作者 Rahul Raveendranath 授权由 InfoQ 中文站翻译并分享。


导读: Clojure (发音类似"closure")是一种运行在 Java 平台上的 Lisp 方言,Lisp 是一种以表达性和功能强大著称的编程语言,但人们通常认为它不太适合应用于一般情况,而 Clojure 的出现彻底改变了这一现状。如今,在任何具备 Java 虚拟机的地方,都可以利用 Lisp 的强大功能,而且在设计上还兼顾了 Java 的互操作性。Clojure 是一种高级的,动态的函数式编程语言。它是基于 Lisp 编程语言设计的,并且具有编译器,可以在 Java 和 .Net 运行时环境上运行。从 20 世纪 50 年代开始至今,Lisp 语言家族已经存在很长时间了。Lisp 使用的是截然不同的 S - 表达式或前缀注释。这个注释可以被归结为 (function arguments…)。通常总是从函数名开始,然后列出要向这个函数添加的零个或多个参数。函数及其参数通过圆括号组织在一起。数量众多的括号也成为了 Lisp 的一大特征。你可能已经发现,Clojure 是一种函数式编程语言。专业人士可能会说它太过单一,但实际上它却囊括了函数式编程的所有精华:避免了不稳定状态、递归、更高阶的函数等。Clojure 还是一个动态类型的语言,你可以选择添加类型信息来提高代码中的关键路径的性能。Clojure 在设计上也考虑了并发性,并具有并发编程的一些独特特性。另外,我们也持续同步翻译了《通过 Lisp 语言理解编程算法》系列文章,有兴趣的读者可以去阅读该系列文章来理解 Lisp 的特性。Lisp 有很多方言,Clojure 只不过是其中之一,但为什么它能够脱颖而出呢?Rahul Raveendranath 分享了他的思考。


Clojure 在我的工作中作为主打编程语言已逾两年,我非常喜欢这门语言!我仍然会遇到很多搞开发的朋友和同事,他们属于这两种情况中的一种:


  1. 从未听说过 Clojure 或 Lisp。

  2. 听说过 Clojure,但将其视为又一个 Lisp 而不屑一顾。


在本文中,我希望就第二种情况讨论 Clojure 的特别之处。但对于第一种情况,我会从一个简短的背景介绍开始。


除非你一直生活在岩石下,否则我想你一定听说过函数式编程(Functional Programming,FP)。近年来,它保持了快速发展的势头,尤其是像 ReactRedux 这样流行的前端框架采用了这种模式。即使是 OOP 的典型代表,从 Java8 开始,Java 也开始支持了函数式编程。现代服务需要能够在全球范围内扩展,通过互联网为全球受众提供服务,并发性就变得有必要了。这就是函数式编程的高光时刻。Lisp 就是一类编程语言,可以追溯到 1958 年,建立在 Lambda 演算 之上,支持函数范式。Clojure 是最接近 Lisp 的语言之一,由 Rich Hickey 于 2007 年创建。


那么,是什么使 Clojure 从其他 Lisp 方言中脱颖而出?


1. 数据结构的持久性和不可变性

Clojure 有一组丰富的数据结构。大多数人都没有意识到的是,在语言 / 数据结构设计方面,这些技术有多先进。这些都是基于该领域数十年进展之上并在其上进行改进的结晶。



随着时间的推移,数据结构不断演变,最终导致了 Clojure 的诞生。来源:《深入研究 Clojure 的数据结构》,Mohit Thatte。


不可变性

在程序员的职业生涯中,大多数不得不面对或修复意想不到的生产缺陷,他们都知道易变性的危害。Java 开发人员已经尝试使用一些措施来解决这一问题,比如,在代码库中遍布 final 关键字等。


Clojure 通过使所有数据结构在默认情况下不可变,从而在更高的抽象级别上解决了这个问题。如果你是这个概念的新手,你可能会想知道,如果一切都是不可变的,该如何让任何人能够完成任何事情呢?理解这一点的关键是认识到价值和身份之间的区别,这些概念在 OOP 和命令式世界中经常被混淆。简单来说,每当需要更改某个内容时,都会创建一个新版本,从使用角度来看,这个新版本与原始版本无关。


如果你考虑如何实现不可变性,一个简单的解决方案可能是,在每次修改整个数据结构时,对其进行复制。但你很快就将意识到这个方案的效率是多么低下。这就是 Clojure 使用的所有高级数据结构(如映射到 trie 或 HAMT 的哈希数组)用武之地。在过去,就像熊掌和鱼不可兼得一样,你必须在不可变性和性能之间做出选择。但现在,有了 Clojure,你可以同时获得不可变性和性能!


持久性

如上所述,使用不可变性,可以随着时间的推移,创建同一数据结构的不同版本。Clojure 实现了不可变的数据结构,你可以获取这些数据的任何先前或当前版本,并进一步修改它们来创建更新的版本,而不会出现任何问题。这一属性就是“持久性”。



在 Clojure 中不同版本之间的数据共享。来源:《实践理论:数据结构的持久性》


2. 可与 JVM(以及。Net 和 JavaScript)进行互操作

Clojure 编译为 Java 字节码,可在 JVM 上运行。我不能强调这对我的团队有多大的改变。一般来说,这使得在公司环境中开始使用 Clojure 变得更加容易,原因如下:


  1. 你可以像在现有 Java 代码库中添加库那样“偷偷”使用 Clojure。

  2. 你或 Clojure 社区不必通过重新创建 Java 中已存在的所有有用库来重新“发明轮子”。


在过去,采用其他 Lisp 时,无法做到这两点,一直是重要的限制因素。而第二点很重要:不仅可以利用 JVM 中的所有库,Clojure 代码还可以与为 JVM 无缝构建的工具 / 框架一起工作。互操作性真的很好。你甚至可以将 Java 方法和接口公开给 Clojure 代码,这样别人就无法分辨出其中的区别(参见 gen-classes)。


以上谈到的几点,适用于 .Net 生态系统以及 JavaScript 生态系统,因为 Clojure 的实现可以编译成各自的运行时格式。JS 版称为 ClojureScript


3. 额外之物:Clojure.spec

Clojure 是一种动态编程语言,和大多数其他 Lisp 一样。其中一个关键的实际含义是,你无需对代码进行静态类型检查。虽然静态类型检查确实有助于偶尔在编译时捕获一些错误,但它有两个主要限制:


  1. 程序员无法选择静态类型的内容 / 位置。一切都是类型。这在代码中造成了大量的冗长和不必要的“噪音”。

  2. 类型系统的表达能力相当有限。只能检查语言设计器提供的数据类型。


除非你亲身体验,否则很难想想还有这样的一个世界,你可以获得比静态类型更强的验证能力,同时还没有这两个限制。Clojure spec 为我们提供了这一点。虽然 Clojure spec 的目标并不是成为一个类型系统,但它远不止于此。


为了说明可表达性的要点,请想想你最喜欢的静态类型语言(例如 Java)中的基本数据类型。你可以使用 IntegerString 数据类型。如果在你的领域 / 程序中,你希望关注具有同级别区别的正数和负数,该怎么办?如果不创建新的类和对象的话,就不能以简单 / 直接的方式进行。如果你的类型定义更复杂或涉及到动态性,那么这将会使情况变得更棘手。


使用 CLojure spec,你可以使用任意逻辑谓词(例如 int?pos? 等)来创建新类型。下面是一个创建名为 pos-int 的新规范的示例代码,该规范描述了一个正整数。


(s/def ::pos-int (s/and int? pos?))
复制代码


你可以选择规范你的重要数据(主要是接口),并验证你是否在运行时轻松接受 / 输出符合规范的数据。规范还支持根据规范自动生成测试数据的生成式测试。与使用单元 / 集成测试中使用任意虚拟数据调用函数不同,你指定了它的输入 / 输出,并使用它自动生成 100 个测试输入,以运行该函数并通过编程方式来验证输出是否符合规范。


结论

还有许多其他理由热爱 Clojure。下面是 Uncle Bob(因撰写 CleanCode 一书而闻名)最近写的一篇文章:Why Clojure? 讲述的是他为什么喜欢 Clojure。然而,以上所述是我认为 Clojure 在其他 Lisp 失败的情况下仍然取得成功的关键原因。


我希望你能够在下一个(爱好或工作)项目中认真考虑是否使用 Clojure。既然你读到这里,我建议你去看看 Rich Hickey(Clojure 的创造者)最受欢迎的演讲:Rich Hickey’s Greatest Hits。即使你最终没有选择使用 Clojure,观看这些演讲肯定会让你成为一名更好的程序员。


“每次观看他的演讲,都有一种醍醐灌顶的感觉。”

——https://github.com/tallesl/Rich-Hickey-fanclub


作者介绍:


Rahul Raveendranath,对科技和创业充满热情,他着迷的技术包括人工智能、增强 / 虚拟 / 混合现实,自然语言处理、编程语言等。


原文链接:


Why Clojure is not just Yet Another Lisp


2019 年 8 月 29 日 17:596567
用户头像

发布了 339 篇内容, 共 130.6 次阅读, 收获喜欢 856 次。

关注

评论

发布
暂无评论
发现更多内容

技术随笔:Rest Api设计中处理业务错误的一些思考

御剑

架构 RESTful

阿里开发7年大牛:Android事件分发机制及设计思路,分享PDF高清版

欢喜学安卓

android 程序员 面试 移动开发

1.1 Go语言从入门到精通:开发环境搭建

xcbeyond

go vscode 环境安装 28天写作 Go语言从入门到精通

工作日志2-20

技术骨干

私有云、公共云、混合云安全性的优点和缺点

浪潮云

云计算

先收藏!关于Java类、接口、枚举的知识点大汇总

华为云开发者社区

Java 接口 枚举

Dapr 知多少 | 分布式应用运行时

圣杰

架构 云原生 k8s dapr

MySQL查看及杀掉链接方法大全

Simon

MySQL

为什么不推荐使用汉字作为密码?

不脱发的程序猿

程序人生 密码学 28天写作 二月春节不断更

Koa中间件体系的重构经验

智联大前端

node.js 前端 单元测试 重构 koa

详解SSH 框架中对象调用流程

华为云开发者社区

spring hibernate struts SSH 框架

测试InfoQ 平台发布文章

木子的昼夜

一文带你熟悉Pytorch->Caffe->om模型转换流程

华为云开发者社区

网络 模型 PyTorch caffe 算子边界

诊所数字化:诊所开展私域运营的优劣势

boshi

医疗 私域运营 七日更 28天写作

超强前端面试真题+资源推荐

evantre

前端 面试题 面经

容器 & 服务:一个Java应用的Docker构建实战

程序员架构进阶

Docker 容器 七日更 28天写作 2月春节不断更

话题讨论 | 你在互联网大厂是个啥级别?

架构精进之路

话题讨论 28天写作 话题王者

我身边的高T,问了Java面试者这样的问题......

京东科技开发者

MySQL 数据库

我与技术面试那些事儿

魔王哪吒

CSS html 前端 28天写作 2月春节不断更

【LeetCode】托普利茨矩阵Java题解

HQ数字卡

算法 LeetCode 28天写作 2月春节不断更

Apache Flink 在快手的过去、现在和未来

Apache Flink

flink

Flink SQL 性能优化:multiple input 详解

Apache Flink

flink

android开发需要学什么!最全面试考点与面试技巧,已拿offer附真题解析

欢喜学安卓

android 程序员 面试 移动开发

还愁追不到女神吗?一键生成舔狗日记,一秒速成舔狗之王

不脱发的程序猿

程序人生 28天写作 二月春节不断更 舔狗文化

Kafka.04 - Kafka 部署

insight

kafka 2月春节不断更

基于证券云服务的总体架构设计应该怎么做?

Jason Tien

技术扫盲:关于低代码编程的可持续性交付设计和分析

小傅哥

Java 小傅哥 服务端 低代码开发 可持续交付

WinDbg 分析高内存占用问题

圣杰

dotnet windbg

日记 2021年2月22日(周一)

Changing Lin

2月春节不断更

配合Github Actions 做一个自动推送的 Rss 订阅机器人

Leetao

Python RSS Github Action

技术解析 | Doris SQL 原理解析

百度开发者中心

百度 Doris SQL优化

Study Go: From Zero to Hero

Study Go: From Zero to Hero

是什么让 Clojure 脱颖而出?-InfoQ