@freeCodeCamp: Clojure 是一种函数式编程语言,能改变你对编写代码的思考方式。在这份互动指南…
摘要
一份互动指南,通过动画代码回放和动手练习介绍 Clojure 编程,涵盖函数、不可变数据、递归等。
查看缓存全文
缓存时间: 2026/07/29 11:56
Clojure 是一种函数式编程语言,它可以改变你编写代码的思维方式。
在本交互式指南中,@PlaybackPress 通过动画代码回放和动手练习教你 Clojure。
你将学习函数、不可变数据结构、递归、惰性序列等内容。
https://freecodecamp.org/news/learn-clojure-programming-basics/…
Clojure 动画入门 – 学习 Clojure 编程基础
来源:https://www.freecodecamp.org/news/learn-clojure-programming-basics/ Clojure 动画入门 – 学习 Clojure 编程基础本教程介绍编程语言 Clojure。Clojure (https://clojure.org/) 是一种运行在 Java 虚拟机(JVM)上的优秀函数式编程语言。它有时被称为 LISP,LISP 是“LISt Processing”(列表处理)的缩写。
你需要具备其他语言的编程经验才能从本教程中获得最大收益。Clojure 与大多数命令式编程语言截然不同,只要你了解基础知识(变量、if 语句、循环和函数),你来自哪种语言背景并不重要。
如果你是寻找完整编程入门指南的初学者,可以在下方找到我的其他编程内容。
在这里,我将介绍函数、数据结构、不可变性、闭包、尾递归、惰性序列、宏和并发。
Clojure 是一种函数式编程语言。在 Clojure 中,函数是核心,数据是不可变的。这可能与你习惯的范式不同,但有一些令人信服的理由使用它。Clojure 的不可变性尤其适用于需要充分利用现代笔记本电脑和手机硬件(多个处理器共享同一内存)的程序。
Clojure 运行在JVM (https://en.wikipedia.org/wiki/Java_virtual_machine) 上。搭建 Clojure 环境可能有些困难,因为需要安装 Java、Leiningen,有时还需要编辑器插件。为了简化,我建议从基于 Web 的 IDE 开始。开始用 Clojure 编程最简单的方法就是使用replit (https://replit.com/)。我建议用它来编写你的第一个 Clojure 程序。如果你准备好了,可以查看这些文档 (https://clojure.org/guides/install_clojure) 在自己的机器上开始。
即使你不每天都用 Clojure,学习它也会改变你对编程的思考方式。它将帮助你以全新的方式理解递归、高阶函数和数据转换。这些思想可以很好地迁移到其他语言,如 JavaScript、Python 或 Rust。简而言之,学习 Clojure 会让你成为更好的程序员。
代码回放
本材料的呈现方式不同于传统的在线教程或视频系列。每个章节都包含交互式代码回放的链接,这些回放以逐步的方式直观地演示程序的变化,帮助你理解代码的编写过程。
代码回放通过回放开发过程中的所有步骤来展示程序的演变。它包含作者提供的叙述、截图、白板风格的绘图以及自评选择题,使学习过程更加动态和互动。
以下是一个简短的 YouTube 视频,解释如何查看代码回放:
Playback Press
Playback Press (https://playbackpress.com/books) 是我创建的一个分享交互式代码演练的平台。顺便说一下,我是 Mark,计算机科学教授。这些书籍通过逐步动画、AI 辅导和测验提供交互式编程课程。
如果你想查看完整的 Clojure“书籍”,可以访问这里:
Clojure 动画入门 (https://playbackpress.com/books/cljbook),作者 Mark Mahoney
我还构建了Storyteller (https://markm208.github.io/),这是一个免费开源的代码回放工具。
AI 导师
查看代码回放时,你可以向 AI 导师提问有关代码的问题。它会清晰耐心地回答问题,是学习者的宝贵资源。你还可以要求 AI 导师生成新的自评选择题,来测试你对所学知识的掌握程度。
要使用 AI 导师和自评测验,只需在 Playback Press 上注册免费账户,并将这本书 (https://playbackpress.com/books/cljbook) 添加到你的书架中。
目录
- Clojure 简介 (https://www.freecodecamp.org/news/learn-clojure-programming-basics/#heading-introduction-to-clojure)
- 函数 (https://www.freecodecamp.org/news/learn-clojure-programming-basics/#heading-functions)
- 闭包 (https://www.freecodecamp.org/news/learn-clojure-programming-basics/#heading-closures)
- 递归 (https://www.freecodecamp.org/news/learn-clojure-programming-basics/#heading-recursion)
- 惰性序列 (https://www.freecodecamp.org/news/learn-clojure-programming-basics/#heading-lazy-sequences)
- 宏 (https://www.freecodecamp.org/news/learn-clojure-programming-basics/#heading-macros)
- 并发 (https://www.freecodecamp.org/news/learn-clojure-programming-basics/#heading-concurrency)
这些最初的几个程序展示了如何向屏幕输出、执行基本算术运算以及存储一些数据。现在逐一学习:
- Hello World!!! (https://playbackpress.com/books/cljbook/chapter/1/1)
- 读取器/求值器与简单算术 (https://playbackpress.com/books/cljbook/chapter/1/2)
- 狗年龄换算器 (https://playbackpress.com/books/cljbook/chapter/1/3)
这个程序展示了如何使用内置于 JVM 的 Java 功能。
- Java 互操作性 (https://playbackpress.com/books/cljbook/chapter/1/4)
这些程序展示了 Clojure 中一些基本的数据结构以及它们如何不可变。
- Clojure 数据结构 (https://playbackpress.com/books/cljbook/chapter/1/5)
- 高效的不可变性(更多数据结构) (https://playbackpress.com/books/cljbook/chapter/1/6)
动手练习
练习 1
编写一个 Clojure 程序,提示用户输入一块木板的长度和宽度(单位:英寸)。然后显示木板的整平方英尺数。例如,如果高是 27 英寸,宽是 34 英寸,那么平方英尺数是 6.375。
练习 2
编写一个程序,创建一个空列表,并使用 def 将其引用存储为 empty-list。使用 cons 将你的名字添加进去,并将其存储在一个名为 my-name 的新列表中。
使用 conj 将所有兄弟姐妹的名字添加到一个名为 me-and-my-siblings 的列表中(如果你没有兄弟姐妹或没那么多,可以使用卡戴珊家族 (https://en.wikipedia.org/wiki/Kardashian_family#Members_of_the_Kardashian-Jenner_Family) 的成员)。
打印 me-and-my-siblings 中所有名字。然后打印 me-and-my-siblings 列表中的第三个名字。
创建一个映射,以所有兄弟姐妹的名字为键,他们的出生年份为值。使用 assoc 将你的名字和出生年份添加到映射中。使用该映射打印每个人的名字以及他们在 2050 年的年龄。
练习 3
创建一个名为 days-in-months 的映射,包含每个月的天数。使用 Clojure 关键字如 :jan 和 :feb 作为键,月份的天数作为值。
从第一个映射创建第二个映射,要求二月有 29 天。将这个映射称为 days-in-months-leapyear。确保高效地完成,使用 assoc 为二月创建新值。最后打印每个映射。
函数
接下来,我将讨论在 Clojure 中创建和调用函数。Clojure 是一种函数式编程语言,因此这是一个非常重要的主题。
前两个程序演示了如何在 Clojure 中编写函数。
- Clojure 中的函数 (https://playbackpress.com/books/cljbook/chapter/2/1)
- Fizz Buzz (https://playbackpress.com/books/cljbook/chapter/2/2)
这个程序展示了如何使用映射来封装数据以及一些操作数据的函数。
- 映射作为对象 (https://playbackpress.com/books/cljbook/chapter/2/3)
这些程序展示了如何使用函数读写文件。
- 从文件读取(附 CS 诗歌) (https://playbackpress.com/books/cljbook/chapter/2/4)
- 写入文件 (https://playbackpress.com/books/cljbook/chapter/2/5)
动手练习
练习 1
编写三个数学函数:
square对传入参数进行平方cube对传入参数进行立方pow计算一个底数的指数幂
对于这一组函数,不要使用任何内置数学函数。
提示:对于 pow 函数,查看 Clojure 的 repeat 和 reduce 函数。使用 let 函数保存临时值,以便后续引用。
练习 2
编写一个函数,接受一个数字并计算该数字的阶乘。
`` 5! 是 5 * 4 * 3 * 2 * 1 = 120
10! 是 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1 = 3,628,800 ``
提示:这类问题通常用递归解决。但现在先尝试不用递归。查看 Clojure 的 range 和 reduce 函数。
练习 3
编写一个函数,判断一个数字是否为质数。使用 range 和 filter 函数过滤掉范围内的非质数。
提示:查看 not-any? 和 mod 函数,判断数字是否为质数。
练习 4
编写一个函数,接受一个或多个字符串参数,将它们转换为数字,然后相加并返回总和。
`` (println (add-strings “10”)) ;打印 10
(println (add-strings “10” “20”)) ;打印 30
(println (add-strings “10” “20” “30”)) ;打印 60
(println (add-strings “10” “20” “30” “40”)) ;打印 100 ``
使用 Clojure 的 reduce 函数或 apply 将字符串转换为数字,然后相加。
闭包
闭包是一种将数据永久与函数关联的方式。
第一个程序展示了如何将数据绑定到函数。
- 闭包 (https://playbackpress.com/books/cljbook/chapter/3/1)
第二个程序展示了使用闭包的一个更复杂的例子。
- 账户利息 (https://playbackpress.com/books/cljbook/chapter/3/2)
动手练习
练习 1
编写一个返回函数的函数。斐波那契数列是通过将前两个数相加生成的数列:
0 1 1 2 3 5 8 13 21 34 ...
通常,数列的前两个数假定为 0 和 1。但在此例中,返回一个使用指定前两个数的函数。然后生成并打印请求的数列中的数字。
`` (defn fibs-fn [firstFib secondFib] …) ;;填充此处
(def another-fib (fibs-fn 10 15)) ;;创建一个从 10 和 15 开始的斐波那契函数
(another-fib 5) ;;打印前 5 个斐波那契数:10 15 25 40 65 ``
递归
递归函数是调用自身的函数。由于每次函数调用都会在调用栈上放置一个栈帧,普通递归有“撑爆调用栈”的风险。使用 recur 的尾递归是一种高效模拟递归且没有缺点的办法。
- 尾递归 (https://playbackpress.com/books/cljbook/chapter/4/1)
- 递归与 Fizz-Buzz (https://playbackpress.com/books/cljbook/chapter/4/2)
- 递归与平方根 (https://playbackpress.com/books/cljbook/chapter/4/3)
- 将字符串转换为 2-16 进制整数 (https://playbackpress.com/books/cljbook/chapter/4/4)
- 抵押贷款计划表 (https://playbackpress.com/books/cljbook/chapter/4/5)
动手练习
练习 1
编写一个递归函数,反转字符串中的字母。
(reverse-string "Mark Mahoney") ;;返回 "yenohaM kraM"
你可能需要创建一个递归的“辅助”函数,它接受的参数个数与递归函数不同,或者使用 loop 形式。确保使用 recur 形式进行递归,以避免“爆栈”。
练习 2
编写一个函数,将一系列字符串用另一个字符串连接起来。该函数应创建并返回一个新字符串。使用带 recur 的递归。
`` (defn join [separator & parts] ;;你的代码)
(join “, “ “Mark” “Laura” “Buddy” “Patrick” “Willy”) ;;“Mark, Laura, Buddy, Patrick, Willy” ``
练习 3
编写一个函数,接受一个整数并将其转换为字符串。用户可指定字符串的进制(2-10)。程序应使用递归和 recur(可以是递归函数或 loop 形式)。
(to-string 100 10) ;;"100" 十进制 (to-string 7 2) ;;"111" 二进制
惰性序列
惰性序列将创建值的开销推迟到需要时才进行。第一个程序展示了如何创建惰性序列。接下来两个是更具体的例子。
- 惰性序列 (https://playbackpress.com/books/cljbook/chapter/5/1)
- 惰性质数生成器(Fizz Buzz 第三部分) (https://playbackpress.com/books/cljbook/chapter/5/2)
- 扑克概率 (https://playbackpress.com/books/cljbook/chapter/5/3)
动手练习
练习 1
创建一个生成惰性平方数列的函数。例如,(1 4 9 16 ... 直至无穷)。用它打印前 10 个平方值。
然后编写一个函数,生成一个惰性序列,其中的值是底数的幂:
`` (defn lazy-pow [start-val power] …)
(take 6 (lazy-pow 10 2)) ;(100 121 144 169 196 225) (take 6 (lazy-pow 10 3)) ;(1000 1331 1728 2197 2744 3375) ``
练习 2
编写一个函数,生成传入序列的无限惰性排列序列。
`` (take 3 (lazy-perm [“a” “b” “c”])) ;(“a” “b” “c”)
(take 12 (lazy-perm [“a” “b” “c”])) ;(“a” “b” “c” “aa” “ab” “ac” “ba” “bb” “bc” “ca” “cb” “cc”)
(take 39 (lazy-perm [“a” “b” “c”])) ;(“a” “b” “c” “aa” “ab” “ac” “ba” “bb” “bc” “ca” “cb” “cc” “aaa” “aab” “aac” “aba” “abb” “abc” “aca” “acb” “acc” “baa” “bab” “bac” “bba” “bbb” “bbc” “bca” “bcb” “bcc” “caa” “cab” “cac” “cba” “cbb” “cbc” “cca” “ccb” “ccc”)
(take 120 (lazy-perm [“a” “b” “c”])) ;(“a” “b” “c” “aa” “ab” “ac” “ba” “bb” “bc” “ca” “cb” “cc” “aaa” “aab” “aac” “aba” “abb” “abc” “aca” “acb” “acc” “baa” “bab” “bac” “bba” “bbb” “bbc” “bca” “bcb” “bcc” “caa” “cab” “cac” “cba” “cbb” “cbc” “cca” “ccb” “ccc” “aaaa” “aaab” “aaac” “aaba” “aabb” “aabc” “aaca” “aacb” “aacc” “abaa” “abab” “abac” “abba” “abbb” “abbc” “abca” “abcb” “abcc” “acaa” “acab” “acac” “acba” “acbb” “acbc” “acca” “accb” “accc” “baaa” “baab” “baac” “baba” “babb” “babc” “baca” “bacb” “bacc” “bbaa” “bbab” “bbac” “bbba” “bbbb” “bbbc” “bbca” “bbcb” “bbcc” “bcaa” “bcab” “bcac” “bcba” “bcbb” “bcbc” “bcca” “bccb” “bccc” “caaa” “caab” “caac” “caba” “cabb” “cabc” “caca” “cacb” “cacc” “cbaa” “cbab” “cbac” “cbba” “cbbb” “cbbc” “cbca” “cbcb” “cbcc” “ccaa” “ccab” “ccac” “ccba” “ccbb” “ccbc” “ccca” “cccb” “cccc”) ``
宏
宏指定要执行的代码,有点像函数,但还允许将部分代码替换为用户提供的值。
宏中的代码有点像模板,可以根据调用者的需求进行修改。借助这个强大的特性,语言可以被扩展,实现语言发明者未曾想过的功能。
- 宏 (https://playbackpress.com/books/cljbook/chapter/6/1)
- Set 宏 (https://playbackpress.com/books/cljbook/chapter/6/2)
动手练习
练习 1
编写一个宏,接受学生作业的成绩(0-100 分)以及一些代码,在成绩及格或不及格时执行。
(defmacro eval-grade [grade if-passing if-failing] ...)
并根据成绩的值使用它来打印或调用函数:
`` (def users-grade 43)
(eval-grade users-grade (println “Passing”) (println “Failing”)) ;;“Failing”
(eval-grade users-grade (praise users-grade) (warning users-grade)) ;;
相似文章
Clojure 速度几乎媲美 C(需借助一些优化)
本文详细介绍了 Clojure 如何借助 JVM 的 Vector API 和精心优化,在 3D 压力测试中达到接近 C 的帧率(仅差 20%),展示了动态语言在热循环中也能接近底层性能。
使用Clojure约一个月后的感想
作者分享了学习Clojure一个月的体验,将其与Common Lisp和Scheme进行比较,并赞赏其一致性和务实设计。
基于Go的Clojure
Glojure是一个开源的、基于Go的Clojure语言解释器,能够无缝访问Go库,并允许嵌入到Go应用程序中。它目前处于早期开发阶段,但已用于业余项目。
混合视觉与文本代码
本文介绍了Hybrid ClojureScript,一种允许开发者混合视觉与文本语法用于领域特定表达式的编程语言,保持了可组合性和静态推理能力。
Clojure:Transducers
Clojure 官方文档解读 transducers——可组合、带状态的转换函数,将序列处理与具体集合类型解耦。