文档教程知识库【免费下载链接】translations Chinese translations for classic software development resources项目地址https://gitcode.com/gh_mirrors/tr/translations点击查看免费下载本篇技术指南以 Bob 大叔Robert C. Martin的经典短文《Pragmatic Functional Programming》中文译文为骨架梳理函数式编程FP兴起的硬件背景、不可变状态带来的并发与心智收益并完整演示 Clojure 的极简语法、JVM 互操作、defprotocol/deftype动态多态与同像性适合想理解为什么学 FP并快速上手 Clojure 的开发者。本文对应译文位于 pragmatic-functional-programming/README.md是 translations 仓库中FP / Clojure / Lisp主题分类下的代表作之一。缘起摩尔定律的尽头与多核时代的到来文章首先回溯了函数式编程风潮的真正起点。大约从 10 年前起开发者开始关注 Scala、Clojure、F# 这类函数式语言而这场热潮并非仅仅出于哇一门新语言的新鲜感背后有实在的硬件原因。摩尔定律曾承诺每隔 18 个月计算机速度翻倍这个规律从 60 年代一直有效到 2000 年左右此后开始失效。时钟频率到达 3 GHz 后进入平台期这触及了光速的物理极限——芯片表面上的信号传播速度限制了计算速度的进一步提升。于是硬件设计师改变了策略为了获得更大的吞吐量在芯片上添加更多处理器核心数为了给新加的核腾出空间去掉了很多缓存caching和流水线pipelining硬件结果是单个处理器变慢了但由于核心数量增加整体吞吐量仍在提升。作者回忆自己 8 年前拥有第一台双核机器两年后换成了 4 核核数进入激增期。当时整个行业都意识到这将以无法想象的方式影响软件开发——而函数式编程正是在这个节骨眼上被重新审视FP 强烈不鼓励在变量初始化之后再改变其状态state这对并发有着深刻影响。为什么 FP 被认为是多核问题的解药不可变状态与并发安全的关系是全文最核心的论证链条如果不能改变变量的状态就不会有竞争条件race condition如果不能更新变量的值value也就不会有并发更新concurrent update问题。这正是多核问题的经典解决方案当核数激增并发甚至共时性simultaneity都会成为大问题而 FP 提供了一种能够减轻单处理器拥有 1024 核时可能出现问题的编程风格。所有人都因此开始学习 Clojure、Scala、F# 或 Haskell仿佛冲锋号即将吹响。然而这一天并没有真正到来。作者六年前买了一台 4 核笔记本比上一台只多了两个核下一台预计仍是 4 核——硬件似乎又进入了另一个平台期。他还用一个 2007 年电影的细节佐证这种感受影片里的笔记本、浏览器、翻盖手机虽然过时但变化幅度远比不上 2000—2011 年、更比不上 1990—2000 年仿佛计算机与软件技术的发展正在进入平台期。由此引出一个务实的问题也许 FP 并不是之前想的那样关键的技能也许不必担心 32768 个核的芯片可以放松下来回到更新变量的老路上Bob 大叔的态度非常明确这会是一个错误一个严重的错误其严重程度可与滥用goto相提并论与放弃动态派发dynamic dispatch一样危险。理由是回到 FP 最初引起兴趣的原因——FP 让并发安全得多。如果要搭建一个包含大量线程或进程的系统FP 会大大减少竞争条件和并发更新引发的问题。在多核规模不确定的当下放弃这个安全性储备并不明智。FP 为什么更容易不可变状态与更少的心智负担除了并发安全作者给出了另一个常被忽视的理由FP 更易写、易读、易于测试和易于理解。听到这里尝试过 FP 的读者可能已经掀桌子——map、reduce、递归尤其是尾递归哪个说得上容易作者承认这本质上只是一个熟悉度问题一旦熟悉了这些概念建立熟悉的过程并不需要太长时间编程就会容易得多。为什么更容易核心在于不需要跟踪系统的状态由于变量的状态无法改变系统的状态也就维持不变不只是系统列表、集合、栈、队列等数据结构也无法改变通通不需要跟踪状态在 FP 语言中向一个栈push元素得到的是一个新的栈原来的栈保持不变。这意味着程序员就像接抛球杂耍需要在空中同时控制的球变少了需要记忆的东西更少、需要跟踪的东西更少因此代码的编写、阅读、理解和测试都变得简单得多。这一观点与仓库中另一篇译文 手把手介绍函数式编程从命令式重构到函数式 相互印证——后者同样强调函数式代码的核心特质就一条无副作用即代码逻辑不依赖也不更改函数之外的数据所有其他函数式特质都由此派生。两份文档从不改变状态与无副作用两个角度指向同一个工程结论限制可变性就是限制复杂度。选择哪门 FP 语言Clojure 的极简主义那么应该用哪门 FP 语言作者的最爱是Clojure理由是它难以置信的简单。Clojure 是 Lisp 的一个方言而 Lisp 是一门简单至美的语言。作者用一句话演示了这种简单性。Java 中的函数调用是f(x)转换到 Lisp只需把第一个括号移到左边(f x)没错就这一下——按照作者的说法你已经学会了95% 的 Lisp和90% 的 Clojure。对这些语言而言这些傻傻的小括号真的就是全部语法了难以置信的简单。针对初学者常见的顾虑作者逐一化解不喜欢满屏括号Clojure 比传统 Lisp 拥有更多符号所以括号相对更少不习惯CAR、CDR、CADRClojure 用first、rest、second替代担心生态Clojure 基于 JVM完全可以使用所有 Java 库和任何想要的 Java 框架与 Java 的互操作快速而便捷担心丢了面向对象Clojure 能够使用 JVM 全部的面向对象功能。Clojure 与 JVM面向对象与 FP 的兼容共存FP 和面向对象是相互不兼容的——这是常见的误解作者直接斥之为胡说八道。在 FP 中你确实无法改变一个对象的状态但那又如何就像向栈push整数后返回一个新栈一样当调用一个方法调整对象的值时返回的是一个新对象而不是改变原对象。一旦习惯了这种做法处理起来很容易。作者进一步指出面向对象在软件架构层面最有用的特性是动态多态性dynamic polymorphism而 Clojure 提供了对 Java 动态多态性的完全使用能力。这一点可以用一个完整的例子说明。动态多态实战defprotocol 与 deftype 对照 Java 接口先用defprotocol定义一个 JVM 层面的多态接口即协议对应译文代码(defprotocol Gateway (get-internal-episodes [this]) (get-public-episodes [this]))在 Java 中这个接口看起来像这样public interface Gateway { ListEpisode getInternalEpisodes(); ListEpisode getPublicEpisodes(); }两者在 JVM 层面生成的字节码是完全相同的。这意味着Java 写的程序可以实现这个 Clojure 协议就像它本身是用 Java 写的一样反过来Clojure 程序也可以实现一个 Java 写的接口。用deftype实现Gateway协议的代码对应译文的完整示例(deftype Gateway-imp [db] Gateway (get-internal-episodes [this] (internal-episodes db)) (get-public-episodes [this] (public-episodes db)))注意构造函数的db参数以及在方法中是如何访问这个参数的deftype会把[db]变成类型实例持有的字段上面的实现只是简单地把协议方法委托给本地函数internal-episodes、public-episodes并把db传进去。实际使用时可以用deftype生成的构造器创建实例并以普通函数调用的方式触发协议方法;; deftype 生成的构造器创建实例db 为某个数据源 (def gateway (Gateway-imp. db)) ;; 协议方法可以直接作为函数调用 (get-internal-episodes gateway)这套机制带来的架构收益是业务代码依赖抽象协议Gateway而非具体实现数据源类型可以随时替换例如从内存切换为数据库这正是动态多态在软件架构层面的价值所在也说明函数式与面向对象完全可以共存互补。同像性代码即数据也许最大的优点来自 Lisp、进而也是 Clojure 的——同像性Homoiconic代码本身就是程序能够操作的数据。这一点不难看出下面的代码(1 2 3)表示一个三个整数的列表如果该列表的第一个元素变成了一个函数也就是(f 2 3)那么它就变成了一个函数调用。可见在 Clojure 中所有的函数调用都是列表而列表可以直接被代码操作。因此一个程序可以构造并执行其他的程序——宏、领域特定语言DSL、代码生成这些高阶玩法都建立在同像性之上。结语学习 FP从 Clojure 开始Bob 大叔的结论务实而明确FP 是重要的你应该去学习它。它不仅是多核时代并发安全的有力工具更通过不可变状态显著降低了编写、阅读、理解和测试代码的心智负担。如果你还在纠结用哪门语言学习 FP他的推荐是 Clojure——语法极简、基于 JVM 生态、兼具动态多态与同像性是一扇非常务实的函数式编程入门之门。延伸阅读本仓库内译序说明本文译于 2018-10参考了 liuchengxu 的译文稿全文以务实的思考引导读者理解、学习和使用 FP并在文末呼吁学习 FP、推荐 Clojure 语言见 pragmatic-functional-programming/README.md 开头部分。姊妹篇手把手介绍函数式编程从命令式重构到函数式——用 Python 从命令式代码一步步重构到map/reduce/管道与本文的不可变状态主线互相印证。Lisp 学习路径Successful Lisp中的 Lisp 书籍推荐 及 Lisp 书籍推荐和点评适合顺着本文的推荐继续深入 Lisp 系语言。目录定位本译文在仓库 README.md 中归入FP / Clojure / Lisp主题分类与上述两篇组成该主题的完整阅读序列。赞分享文档教程知识库【免费下载链接】translations Chinese translations for classic software development resources项目地址https://gitcode.com/gh_mirrors/tr/translations点击查看免费下载相关推荐在ROCm的hipBLAS中使用BF16数据类型进行矩阵乘法的注意事项在ROCm的hipBLAS中使用BF16数据类型进行矩阵乘法的注意事项 在ROCm生态系统中使用hipBLAS库进行高性能矩阵运算时开发者可能会遇到数据类型转文档教程知识库NPU加速实战Lightweight_OpenPose单卡/8卡训练性能优化与精度对比终极指南NPU加速实战Lightweight_OpenPose单卡/8卡训练性能优化与精度对比终极指南 在人工智能计算领域 NPU加速技术 正在彻底改变深度学习模型Clojure 快速入门JVM 上的 Lisp 函数式编程从核心语法到并发实战Learn X in Y minutes 教程精讲Clojure 快速入门JVM 上的 Lisp 函数式编程从核心语法到并发实战Learn X in Y minutes 教程精讲 本篇技术指南以 lea文档教程上一篇libopenapi性能基准测试与其他Go OpenAPI库的对比分析下一篇零基础玩转MM-EUREKA自定义数据集格式与训练脚本全解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考