原文:Why is Learning Functional Programming So Damned Hard?
我的函数式编程之旅充满了死胡同、错误的开始、失败的尝试和挫折。而且我怀疑在这场斗争中我并不孤单。那么,为什么这是常见的问题,我们可以做些什么呢?你该如何避免这些相同的陷阱?
TLDR:我大约在 2021 年的解决方案,让函数式编程变得更简单:循序渐进指南。
学习编程
我最初的编程之旅与我的函数式编程之旅截然不同。在别人拥有个人电脑之前,我就开始了。我从为我的德州仪器计算器编程开始,最终得到了我自己的电脑,它把程序保存在盒式磁带上。
我很幸运,因为我可以在这个行业中学习和成长。我作为学生学习了 FORTRAN、Basic、COBOL、6502 汇编程序和 Pascal。
在我 19 岁获得第一份编程工作后,我学习了 Z80 汇编,然后学习了 C,后来又学习了 C++。在那些日子里,没有可供下载的免费 Library 生态系统。你必须自己编写一切。因此,你从来没有期望过我们今天期望开发人员生产的产品。
十年后,出现了 Java、.NET 语言和 JavaScript。一路上还有许多其他语言,但这些是里程碑式的语言。
当我确实开始编程时,我可以慢慢学习新概念。但更重要的是,每种语言都建立在我已经从以前的语言中发展起来的知识之上。诚然,当我第一次打开 C Programming Language 书的第一版时,我发誓我看到的是象形文字。但这更像是一个语法问题。这些概念与 FORTRAN、Basic 和 Pascal 没有什么不同。
学习过程很简单,我现在意识到,这很不寻常。大多数进入编程领域的人,都不是 60 后出生的,而是一开始进入了一个成熟的行业。直到发生两件重大的事情,我才意识到自己(所处的环境)有多好。
首先是教我女儿编程。当我坐下来计划这个过程时,我意识到重要的不是我知道多少,而是她需要知道多少才能找到一份高效开发人员的工作。
当我开始编程时,我被期望编写一些 Z80 汇编代码来将字符打印到打印机。当她开始第一份实习工作时,她需要用 Node 编写一个服务器来监控网站。
第二个让我真正意识到学习编程有多么困难的事情是当我尝试学习函数式编程时。 30 多年来,我第一次成为 NOOB。
我们不在堪萨斯了
有一年圣诞节,当我还是个孩子的时候,我的父母打算给我买一台 HP 41CV 计算器。大约是在我开始在大学的 Univac 大型机上学习编程的时候。因此,为了鼓励我,或者更可能是阻止我目前成为理论物理学家的职业计划,他们找到了一台二手电脑,只比计算器多了 500 美元。
他们给了我一个选择。计算器或计算机,但我必须向他们支付 500 美元的差价。我不确定,所以我们快速去了当地的电脑商店。
当推销员说出我只会发音的单词时,我的两侧是每位家长:RAM、ROM、ASCII、8K、bytes、Basic、显示器、处理器等。当我努力掩饰自己的困惑时,我的父母看着我们喜欢它是一场网球比赛。首先是推销员,时间刚好足以让人不知所措,然后回到我身边,看看我是否在跟进。然后回到推销员。行话的冲击在我的额头上来回弹跳。
当售货员借口在收银台帮助顾客时,我父母问我:“你听懂他说的了吗?” 我顿了顿说:“我一个字都听不懂…… 我要电脑。”
那天晚上他们给我买了电脑。回到家后,我把它放在电视托盘上,开始慢慢地、有意识地阅读那本寥寥无几的手册。我以前从未见过电脑,虽然我不害怕它,但我不想犯任何错误。
一旦我弄清楚如何连接显示器并打开它,我对那台机器所做的第一件事就是添加两个数字。我直接从手册上仔细打字:
PRINT 5 + 2
它显示了数字七。我愣了一下,然后对在厨房做饭的妈妈大声喊道:“喂,妈妈!你不会相信的。这不仅仅是一台电脑。还是计算器!”
我们不再在堪萨斯州了 —— 续集
快进 30 年。正如人们所预料的那样,我不再和父母住在家里了。我有家庭和职责。作为一家小公司的 CTO,我的工作要求很高。
与年轻的我不同,我不能每天花 10 个小时在校园的计算机实验室里玩耍 / 学习。我有现实世界的需求。人们在工作和家里都依赖我。经过一整天的工作,我已经把大部分脑力都用在了工作上。所剩无几的大部分时间都花在了家庭生活上。
正是在这种气氛下,我的经历与那天在电脑商店的经历非常相似。我一直在阅读有关函数式编程的内容。当我研究它时,我发现 Haskell 是那个领域的事实标准。
所以,我天真地决定再学一门语言。虽然下班后我没有太多时间或精力,但周末我确实有时间,所以决定看看这个 Haskell 到底是怎么回事。
这有多难?在我职业生涯的这个阶段,我已经学会了几十种语言。每一个都比上一个更容易学习。通常,每一种新语言都会增加,也许,一两个新想法。我预计这会很快进行。
我错了。
就像在推销员的演讲中,我撞到了术语墙,Monoid,Functor,Currying,Lambda,Purity,Side-Effects,Monad 等。但与第一个术语挑战不同,我的反应非常不同。
我退缩了。我偏转了。我责怪那些学术上的书呆子和他们的花言巧语,他们的唯一目的是排斥和证明他们的精神优势。
安慰奖
我通常不会轻易放弃。令我苦恼的是,在这个行业工作了 30 多年后,我还是没能学会一门糟糕的编程语言。所以时不时地,我会受到激励并再次尝试。就像以前一样,我会从术语墙中弹开。上网搜索也无济于事,因为似乎所有了解 Haskell 的人都感染了术语症。
所以我决定学习函数式 JavaScript,因为它可以做一些函数式的事情,例如 JavaScript 将函数作为一级对象。所以我尝试了 Currying,尽管我不明白为什么会有人想要做这样的事情。
我尝试编写自己的函数式 JavaScript 库,主要用于学习。然后我发现了 Ramda,这是一个函数式 JavaScript 库,它比我正在使用的库要好得多。
要完全理解将 JavaScript 用作函数式语言的感觉,您必须想象用鞋后跟敲钉子。是的,钉子被缓慢而痛苦地钉入木头,但钉子和鞋子都不适合它。
幼儿园
我在工作中教程序员一些更简单的函数式思想以及如何使用 Ramda。有一段时间,我们都像以前一样编写 JavaScript,但现在我们偶尔会用一些函数代码来调味。
不久之后,当我们为一个新项目进行技术研究时,我们遇到了 Elm。当我们这样做时,那是一股清新的空气。 Elm 并没有使用行话。当然它看起来像 Haskell,但我不需要博士学位也能理解它。
我爱上 Elm 就像溺水的人爱上了救生圈。它使我免于学习 Haskell 及其所有难懂的概念。
Elm 教给我的是如何纯粹地思考,即你不能改变你的任何变量。它教会了我如何更实用地编写代码。它让我想起了静态类型的巨大好处,而没有 Java 中类型的笨拙。
我想我会在余下的职业生涯中或至少剩下的大部分时间里都在 Elm 编程。我甚至创建了一个开源工具来帮助在服务器上编写 Elm,这不是该语言创建的目的。
当我们遇到不同类型的墙时,我们的下一个项目有超过 30,000 行 Elm 代码。
不要拿迈克来质问这个人
我非常酷的开源工具得到了一些关注。但并没有像我希望的那样受到关注。
我的目标是帮助其他人做我们正在做的事情,即编写服务器端 Elm。我想支持 Elm 并帮助它取得成功。而这个工具只是该意图的一部分。
不幸的是,语言的创造者并不太高兴我让它变得简单,因此,鼓励人们以他所说的方式使用他的语言,它从来没有打算被使用。
他试图向我解释说有更好的语言,例如 Haskell 和 PureScript,做我们试图用 Elm 做的事情(他是对的,有比 Elm 更好的语言)。但我不想要那些其他语言。我想要一种简单的语言。而且我已经知道 Elm,这很容易。
他告诉我,他计划在下一个版本中取消我们在编译器级别做的事情的能力。至少可以说,我不开心。最重要的是,我很生气。
我们公司的另一个程序员想 fork 编译器并取消限制。现在我骨子里是个反叛者,虽然这在理论上听起来不错而且幻想起来很有趣,但我知道最好不要用麦克风质问那个人。你总是输。
大学
因此,超过 30,000 行代码进入了 bit bucket。
怎么办?
我再也回不到使用 Node.js 用 JavaScript 编写服务器了。不是在 Elm 之后。我被宠坏了。我看到了 bug 的巨大减少,享受着阅读和编写函数式代码的速度和轻松,并珍惜自信地毫不费力地重构的能力。我从来不想回去。
我们仍然可以在 Elm 中编写前端代码。不幸的是,由于技术原因,我们无法使用 30,000 行代码中的任何前端代码。
但是对于服务器,我别无选择,只能毕业于 Haskell。
在这一点上,我曾尝试在 3 或 4 个不同的场合学习 Haskell。所有的失败。从 Elm 到 Haskell 就像从幼儿园到大学。那么是什么让我认为这次我可以做到呢?
这一次,我别无选择。我有真正的理由去学习它。这就是游戏规则的改变者。
动力会让我们几乎忍受任何事情,在接下来的 3 个月里,我一直在学习 Haskell。这是我的工作,也是我的爱好。这是我职业生涯中必须做的最艰难的事情。
整天就像在泥里打滚。只是疯狂地挥舞着,没有真正的方向。从一本书到另一本书。博客到博客。一天结束的时候,我从泥地上站起来,几乎没有注意到它粘在我的皮肤上。
第二天,我会重复这个过程,打滚打滚。到一天结束时,我身上只有极少量的泥浆。感觉好像没有一个粘住。
这持续了数周,最终持续了 3 个月。我会在一天开始时在我的浏览器中只打开几个选项卡,到最后一天我会有 20 或 30 个,因为我掉进了兔子洞。我把它比作只用德语词典学习德语。
我生活在每一个现代的、未来的程序员都非常了解的噩梦中。我不明白的经历,因为我在不同的时间长大。要知道的实在是太多了,而你的知识还不足以知道如何确定学习的优先顺序。
问题是每个人对什么重要都有不同的看法,当你在网上阅读评论时,你无法知道该相信谁。
很多高级用户给像我这样的菜鸟提建议,就好像我们是高级的一样。把这样的建议记在心里浪费了无数个小时的沮丧,因为我不知所措,我不知道。
书籍、文章、wiki 和博客文章都处于不同的进步水平。当你开始时,其中大多数都是禁止进入的。入门书太入门了。你可以通过它们,在你理解的错觉中。然后你开始阅读别人的代码,发现自己完全迷失了方向。
但即便如此,我还是从痛苦的 3 个月中成为了一名 Haskell 程序员。嗯,初学者 Haskell 程序员。我仍然不太了解 Monad。但我可以使用它们。对于我当时的需要,这已经足够了。
Good Enough
现在我可以用 Haskell 编写服务器代码,并且我们的前端有 Elm,我们开始竞相开发我们的产品。但在我用 Elm 培训我们的 JavaScript 开发人员之前。
在经历了从 Elm(一个以没有行话而自豪的环境)到 Haskell(众所周知的行话原始汤)的痛苦之后,我并没有回避在课堂上使用 Functor、Applicative 和 Monads 这些词.
我这样做是为了让他们对这些话感到麻木。你当然可以在没有听过这些话的情况下学习 Elm。我做到了。但是,如果我的学生必须学习 Haskell 或 PureScript,我不希望这些词引起与我一样的恐惧。这是一件好事,因为我们将在下一个项目的前端转向 PureScript。
经过漫长的艰苦旅程和大量的血汗和泪水,我们现在有了一个在前端和后端都使用纯函数式语言构建的产品。这并不容易,但值得。
为什么它已经这么难了?
学习函数式编程困难的原因并不单一,但这里有一些大问题(你的里程可能会有所不同)。
Old Dogs
对于像我这样长期使用命令式语言成功编程的人来说,这尤其困难,因为你必须忘掉很多东西。这几乎就像你正在从头开始学习一项完全不同的技能。
大多数专业人士都属于这一类。许多人在面向对象的模型中有额外的思考负担。因为我在 25 年前就放弃了 OO,所以我并没有为此感到负担。 (参见再见面向对象编程)
新程序员学习函数式编程可能比教 “Old Dogs” 容易得多。
放学
另一个主要问题是教育系统。一个很好的例子是 Monoid。这个概念来自抽象代数以及另一个称为 Semigroup 的有用概念。
这些概念听起来很难,但实际上很简单。程序员几乎每天都在编写代码时与 Monoids 打交道。他们只是不知道。我可能会走进任何七年级的代数课,并在一次讲座中教给他们这个概念。
我在 7 年级代数课后的 40 多年里学到了这些概念。说到数学,我也不懒惰。在大学里,我在 4 个学期的微积分和一个学期的线性代数和微分方程中获得了 A 和 B。
但那些都是旧数学。嗯,所有的数学都是古老的,但这些数学课程是为工业革命世界而不是我们今天生活的信息世界而设计的。我们应该教授一套截然不同的数学。
教育系统也患有躁郁症。他们要么只教授理论,以至于毕业生连一行代码都写不出来。或者他们教人们只编程。他们使用鼎盛时期已经过去的语言来执行此操作,因为该行业必须使用这些语言维护数百万行代码。
这是短视的,会减缓行业的进步。
专家,专家窒息吸烟者
互联网上有太多人冒充专家。大多数意思是好的。但结果是,由于那些认为他们可以教但不能教的人,在 Internet 上学习编程,尤其是函数式编程变得更加困难。
当我开始教我女儿编程时,我很快就知道我的教学有多糟糕。我在教她编程的同时学会了如何教书。
许多人认为他们完全理解某件事,直到他们不得不向其他人解释或教导它。这就是小麦与谷壳的区别。教书的人太多了。
在众所周知的 Monad 教程中可以看到这种现象的一个完美例子。开玩笑的是,一旦有人了解了 Monad 是什么,他们就会体验到两件事。第一,编写 Monad 教程的无法控制的冲动;第二,他们失去了向任何人解释什么是 Monad 的能力,即使是那些已经了解它的人。
我读过很多,大多数都很糟糕。为了不甘示弱,我甚至写了自己的《如何思考 Monads》。希望我的没有大多数人那么可怕,但也许不是。
另一个专家问题是,Haskell 是一种既适用于专业发展又适用于语言研究人员的语言。菜鸟很难区分哪些主题是常用的 Haskell 主题,哪些是深奥的或实验性的。
预订他们,Danno!
书籍曾经是学习技术的途径。作为一个老人,我必须说我怀念那些日子。有点。能够撰写类似本文或博客文章的内容并立即免费发布,这将带来巨大的好处。
这使很多人可以从您的知识或经验中受益。与书籍不同,进入门槛几乎为零。这也是他们最大的缺点。
通常,书籍的编写和编辑要好得多,而且通常结构合理。更多的时间花在他们身上,有一种永恒的感觉。因此,人们通常会更加小心地创建它们。
对于一本书,您不能只是打开浏览器窗口然后开始打字。嗯,从技术上讲,你可以。我在 3 个月的重现期间读了一篇超过 1000 页的文章,如果他们有一位优秀的编辑同时也是一名函数式程序员(也称为独角兽),那么很容易就能达到 300 页。
耐心点,年轻的蚱蜢
我只有 3 个月的时间来学习足够多的 Haskell 来编写一个相当复杂的服务器。这不是理想的情况。众所周知,时间压力让学习变得非常困难。
不要等待学习这些概念。多年来慢慢学习它们。这变得越来越容易,因为其他语言正在慢慢采用一些功能概念。
另一件事是按正确的顺序学习。
今天,如果我想从头开始学习函数式编程,我会按照以下顺序进行:
- 函数概念,例如纯函数、组合、柯里化、不变性等(作为开始,请参阅我的 6 部分系列,所以你想成为一名函数式程序员)
- Elm
- PureScript
- Haskell
我总是说 Elm 就像幼儿园,PureScript 就像高中,而 Haskell 就像大学。
记得我是如何从幼儿园到大学的。不容易。这不是一个好主意。按照复杂性的递增顺序学习要好得多。
我可以说学习 PureScript 让我成为了更好的 Haskell 程序员。想象一下,如果我按照递增的顺序学习,事情会变得多么容易。
说了所有这些并不排除直接进入 Haskell 或 PureScript。但除了全新的程序员之外,我不会建议任何人这样做。一旦你的大脑被命令式编程感染了 2 到 3 年,在你学习之前就需要大量的能量来忘却。你做的时间越长,你需要付出的努力就越多。
可能的解决方案
我说过 Haskell 是我职业生涯中最难学的东西。更准确地说,在一个现在严重落后于计划的项目中扔掉 30,000 行代码后的 3 个月内学习 Haskell 中使用的函数式编程概念是我职业生涯中必须做的最困难的事情。
学习函数式编程并不一定很难。有一个按进步排序的精选资源列表会很有帮助。我们还需要更好的教程和学习材料。
大多数教程是由两类人编写的,专家或新手。专家在几乎每个人的头脑中交谈,因为他们对更高级的主题感兴趣。他们忘记了不理解某事是什么感觉。
然后是新手,他们很高兴刚学会一门学科,他们想告诉全世界。他们的教程往往构思不佳且不完整,而且通常充满错误。
但是,如果我必须选择我的毒药,我会和新手一起去,因为我至少可以理解他们在说什么。但理想情况下,已经使用这些概念工作了几年的在职开发人员将成为老师。他们已经足够先进,可以很好地理解这门学科,但还不够先进,以至于他们忘记了最初学习是多么困难。
当我教我女儿编程时,她问了我一个很简单的问题:我们为什么要使用函数?这个看似简单的问题让我不寒而栗。我已经使用函数这么久了,我真的从来没有想过它。但最糟糕的是,我无法给她一个很好的答案。我花了太长时间才想出一个简单的理由。到那时,她已经自己弄明白了,甚至后来对最初问题的天真感到惊讶。
我失败得如此惨烈的原因是因为我遇到了大多数人在尝试教授函数式程序员时所做的事情。我是有经验的。 (参见为什么专家会成为坏老师)
这真的值得吗?
学习函数式编程值得所有这些麻烦吗?
要回答这个问题,我们必须考虑两件事,购置成本和使用成本。
购置成本
这就是函数式编程昂贵的地方。学习可能需要很长时间,例如学习 Haskell 的时间大约是学习 Javascript 的 3 到 5 倍。
要理解这一点,请想象两个人决定学习编程。一个选择 Haskell 作为他们的第一语言,而另一个选择 Javascript。
Haskell 程序员必须在多个编辑器和构建工具之间进行选择。这一步非常困难,因为他们不知道如何区分。首先,他们必须下载一个编辑器并学习使用它。接下来,他们必须下载构建工具并学习使用它们。
Haskell 程序员可以编写规范的 “Hello, world” 程序,但他们不会完全理解他们几个月来编写的内容。我开玩笑说,当我学习 C 时,我学到的第一件事是 “Hello, world”,而当我学习 Haskell 时,它是最后一件事。
将此与 Javascript 程序员的经验进行对比。他们所要做的就是打开浏览器。然后他们必须学习如何访问浏览器的控制台。最后,他们输入 “Hello, world” 程序。他们可以在几分钟(最多几小时)内理解他们编写的所有代码。
进入 Javascript 的门槛几乎为零。几乎每个人都可以访问 Javascript,因为它在每个浏览器中都可用。如果您编写一个简单的 Javascript 程序,则无需学习任何困难的概念即可学习编码。最终,您将不得不安装一个编辑器,但这不是开始的必要条件。
另一方面,Haskell 的进入门槛要高得多。您必须先安装编译器,然后再安装编辑器。您现在已准备好输入您的第一个程序,但您需要编写您几个月都不会理解的代码来简单地输出您的代码结果。
Haskell 要求您学习从抽象代数到范畴论的概念。 Javascript 具有简单易学的机制,而 Haskell 的机制虽然更强大,但也更难学习。这意味着 Javascript 程序员的工作效率比 Haskell 程序员快 5 到 10 倍。
使用成本
这是 Javascript 和 Haskell 交换位置的地方。
使用 Javascript 编程的成本是您每天付出的代价。创建错误非常容易,直到为时已晚,很多时候在代码投入生产后才发现。
技术债务,即复杂且难以维护的大型代码库的成本和负担,在 Javascript 中也更高。原因之一是语言很脆弱。
一旦支付了沉重的前期成本,Haskell 就开始支付红利。仍然有可能写出糟糕的 Haskell,但是用 Haskell 编写的代码质量永远无法用 Javascript 实现。
Haskell 的代码量也小得多。那是因为它强大的抽象机制。正是这些机制让我们的学习变得如此困难。现在所有这些都已经过去了,我们能够利用这些强大的抽象来编写几行代码来完成更多的工作。
我总是开玩笑说在 Javascript 中你思考 2 分钟,编码 2 小时。但是在 Haskell 中,你思考 2 小时,编码 2 分钟。
学习 Javascript 的成本非常低,但根据代码库的大小,每天的使用成本可能是天文数字。
Haskell 一开始很难学,但每天的成本要小得多,用 Javascript 编程的总成本很快就会超过 Haskell 的总成本。而且,您编程的时间越长,差异就会越大。
投资
使用函数式编程语言进行编程是一项长期投资,其中大部分成本是预先支付的。这意味着您必须相信您听到的关于好处的故事,因为您在很多个月内都不会亲身体验这些好处。
命令式语言,如 Javascript,是一种短视的投资。起初,选择使用 Javascript 感觉就像你做出了正确的决定,因为当你的 Haskell 同行仍在尝试从抽象代数中学习一些晦涩的概念时,你和你的同事正在完成真正的工作。
但是编程并不是写了一次代码就忘记了。我们程序员总是在修改代码来修复它、扩展它的功能、提高它的效率等等。从本质上讲,这是一个长期的承诺。
那么,学习函数式编程值得吗?
当您进行数学计算时,函数式编程的开发成本更低,并且每次您必须更改代码时都会带来红利。与命令式的同行相比,您还拥有一支技能更高的团队。如果这些对您和您的企业有价值,那么它就物超所值。
结论
我已经看到很多技术变化来来去去,而且通常走在技术曲线的前面。 (互联网除外。不知何故我错过了那个。)
我相信函数式编程远比命令式编程好得多。我知道这一点是因为我愿意连续 3 个月受苦学习我过去多次失败的东西,这样我就不必用 JavaScript 编写了。
那里有很多资源,即使它们被埋在大量平庸的材料中。希望函数式编程语言能够获得足够的普及,从而激发具有适当经验的开发人员为教育材料生态系统做出贡献。
与此同时,我们注定要搜索互联网,从维基、博客和论坛帖子、学术论文、书籍等中拼凑出我们自己的课程。但即使困难重重,我认为这仍然是值得的。
我相信每个程序员都会从学习函数式编程中受益,如果你的编程生涯还剩下 10 年以上,你最终将不得不学习它们。
你可以按照自己的时间表或其他人的时间表学习它们。相信我,别人的并不好玩。一点都不好玩。