为什么学球拍?学生的观点

2022-02-22 03:29:48

~/blog/为什么要学球拍I';我是爱荷华州一所小型文科学校格林内尔学院的学生。在这里,计算机科学系在其入门课程中使用Racket,该课程侧重于函数式编程,面向所有学生(专业、非专业,以及有或没有编程经验的学生)去年秋天我自己修完这门课后,这学期我作为这门课的学生导师,在课堂上帮助学生做实验,并每周举行复习课。

格林内尔并不是唯一一个选择在CS入门课程中使用Racket或Scheme的人。也许最著名的计划课程是麻省理工学院';s 6.001开始于1980年,使用《计算机程序的结构和解释》一书教授计算机科学。2008年,麻省理工学院用Python课程取代了他们的Scheme课程,但布朗大学、东北大学和印第安纳大学等其他大学在第一门课程中保留Scheme或Racket。不过,也许是在麻省理工学院之后';因此,在CS本科生项目中,在入门课程中使用球拍的情况仍然相对少见。

考虑到球拍是';Grinnell的几名学生,通常是那些有过Python或Java等更流行语言经验的学生,在上课时会问为什么会使用它';她被选为格林内尔';这是第一道菜。我';我也听到过沮丧的学生想知道为什么他们';你把时间浪费在";无用的";语言,当他们可以学习一门语言,这将帮助他们找到一份工作。我们的教授给出了问题的答案,我还有其他资源';我见过马修·巴特里克';这首曲子很棒,为什么要拍?为什么口齿不清?,但在这里我';I’我想就为什么我认为学习球拍是值得的这一点分享我自己的看法。

我认为球拍是一门很好的第一语言,但在这篇文章中,我';I’我想把重点放在程序员身上,不管是大学毕业还是其他,他们对Python、JavaScript或Java等主流语言有一定的经验,但对学习Racket等函数式编程语言的价值犹豫不决或不确定。

Racket和Lisp通常被吹捧为语法最简单的语言。在Racket中,每种形式都是一个表达式,它要么是一个原子值,比如字符串或数字,要么是一个以过程或特殊形式开头的列表。这种语法与Python这样的语言形成了对比,Python有严格的规则,规定哪些形式可以用作表达式,哪些形式必须是语句。

我认为Lisp爱好者有时夸大了这一优势。虽然蟒蛇';s的语法可能不那么简单,一旦用户学会了它的语法规则,它确实有自己的优雅。然而,Lisp风格语法的优势在于它的心理评估模型。在计算Racket代码时,我们总是首先计算最里面的括号,用适当的主体替换过程调用,除了一些特殊形式。

为了评估这一点,我们将map替换为其定义的主体,替换适当的参数,然后继续正常评估。我们可以这样追踪一个缩写版本:

(地图sqr和#39;(2 4 8))->;(如果(空?';(2 4 8))空(cons(sqr(car';(2 4 8))))(地图sqr(cdr';(2 4 8)###)->;(cons 4(地图sqr和#39;(4 8))->->;(cons 4(cons 16(cons 64 null))->'( 4 16 64 )

沿着这条轨迹,我们看到地图并没有什么神奇之处,它';这只是一个像其他程序一样进行评估的程序。现在将其与Python中的等效列表理解进行比较:

我们如何在心理上评估这一点?为此,我们需要记住具体的#34;神奇的";用于评估Python的语法和语义';s表示理解表达式,它看起来相似,但与循环语句的行为完全不同。

Racket也有一些特殊的形式,比如let和if,必须学习它们的评估策略,但它们通常比Python或Java中的语法更加有限和直观。

只需几行简单的Java';s规范";你好世界";例如,我们';我已经用特殊的求值规则(public、class、static、dot操作符等)做了几个声明。在Java程序员了解他们的程序实际执行的大部分内容之前,必须理解这些规则。

Java是我学习的第一种编程语言,因此我从第一手经验中知道,其结果是该语言的初学者通常只接受他们可以';我无法完全理解这样一个简单的程序是如何评估的。他们';我们不得不接受,学习编程需要记住看似任意的规则来构建代码。初学者Racket程序员不会面临这样的压力,因为它的评估更加直观,我认为这在作为教学语言使用时是一个明显的优势。

即使你是一个经验丰富的Python或Java程序员,确切地知道如何评估语言的每个部分,我仍然认为接触Racket#39;s的评估模式是值得的。对Racket代码的转换进行建模,可以帮助程序员了解不同代码片段如何以其他语言的用户通常不知道的方式分解和组合在一起;t直接考虑。我认为,加强这些基础知识可以建立直觉,从而快速掌握其他语言的评估。

学习Racket通常需要使用比其他语言提供的工具更有限的工具。例如,循环和变异是其他语言的核心功能,对于初学者来说通常是不允许的

虽然一门以小核心构建的语言有真正的美学优势,即如何简洁地定义和实现它,但我认为它';与小型语言的物质优势相比,不要夸大这些优势也很重要。

Racket中对极简主义的关注最初甚至迫使有经验的程序员在如何构造代码以及最终如何解决问题上走出他们的舒适区。对于经验丰富的程序员来说,我认为学习解决问题的根本不同方法是非常有价值的。这种经历使你的人生达到了极限';s的知识,增加了a";一袋把戏";使人能够解决更困难或更新奇的问题。

让';让我们来谈谈递归,因为我认为学习使用递归作为一种通用的控制结构是获得球拍流畅性的最重要的结果之一。

当其他语言的程序员被引入递归时,它';这通常是为了展示一些问题的替代和小众解决方案。给出了计算阶乘数或第n个斐波那契数等典型例子,尽管这些玩具例子对一般编程来说似乎毫无用处。此外,由于堆栈溢出问题,程序员经常被警告不要使用递归。

然而,递归是一个比这些入门教程功能强大得多的工具。事实上,在没有循环和变异的情况下,递归是Racket中用来管理重复和状态的主要工具,对于初学者和经验丰富的程序员来说,学习如何使用递归是学习该语言最具挑战性的方面之一。

使用递归编程的关键在于';它可以用来实现递归数学定义,但是它';It’这是把一个难题分解成几个简单问题的通用工具。事实上,这种分解成子问题的方法是允许递归工作的。

让';让我们看一个例子,比较求和过程的迭代和递归设计。以下是Python中的标准迭代方法:

这种方法直接源于大多数人会如何回答这个问题#34;你如何对一系列数字求和" 好的,从零开始,然后将每个值加到总数中,直到我们到达终点。

然而,递归地实现sum需要不同的思维方式。相反,我们的策略是提出两个问题:第一,34;什么';一张空名单的总和是多少" 好吧,显然是零。其次是";如果我们知道列表中除了第一个元素以外的所有元素的总和,那么';总数是多少" 在这里,我们可以通过将第一个元素的值与其余元素的和相加来找到这一点。

对于不熟悉递归设计的人来说,这种思路肯定不是直观的。然而,它重要地迫使我们将设计分成两个子问题:首先解决基本情况,然后将递归步骤分解为递归调用可以处理的内容,以及我们在每个阶段必须做的事情。

认识到递归思维的价值可能很难,因为它';为什么采用一种天生不那么直观的策略来解决问题是有帮助的,这并不明显。但递归的美妙之处在于,它让我们能够用本来就不那么强大的工具(没有变异或循环)来解决难题和管理状态,以及这种简单性如何奖励有经验的程序员快速解决难题的能力。

递归不是';不是银弹,但它';这是一个基本的工具';这是值得理解的,即使函数式程序员通常更喜欢使用基于递归的更具体、更安全的工具,比如高阶过程或理解。

即使所有关于递归的说法都是真的,人们可能仍然想知道,当递归在其他所有编程语言中都可用时,为什么要选择Racket。答案是,Racket和大多数其他函数式语言一样,旨在使递归设计更符合人体工程学和自然。

首先,每个Racket过程隐式返回最终表达式,因此不可能编写一个不';t返回一个值。此外,该语言中的核心表单旨在阻止命令式编程。例如,if表达式必须包含truthy和false分支,因此它';对一个人来说,忘记包含一个基本案例更难。

然而,也许最重要的是列表数据结构的核心位置。列表是一种固有的递归数据结构,因为它们';re要么为空,要么为某个值的cons,再加上另一个列表。对cons列表进行递归设计是很自然的,所以它们在球拍中的中心位置使得使用递归成为一种自然选择。

将其与在Python等语言中使用递归进行比较。在那里,主列表数据结构是一个索引动态数组。尽管人们可以将使用Racket中的cons和cdr编写的递归算法翻译成Python,但这样做不仅不自然,而且违背Python的精神。事实上,翻译成其他流行的命令式语言也是如此。

从性能的角度来看,Racket对递归设计是有利的,而不仅仅是教学法。重要的是,Racket保证了尾部调用优化,这意味着设计用于利用尾部递归的递归函数将赢得';不要像在Python和Java等命令式语言中那样在堆栈上爆炸内存。

既然我已经赞扬了学习Racket的优点,我想补充一点,编程语言的选择没有你想象的那么重要。我从同学们身上看到的挫折感主要集中在以下问题上:";既然我可以花时间学习一门人们实际使用的语言,为什么我要学习Racket呢" 从表面上看,这个问题有些道理。时间和精力是稀缺的资源,应该合理分配,但我认为提出这个问题忽略了一个关键点:编程比编程语言更难学。

我不';I don’我不想贬低学习一门新语言所需的努力,尤其是像Racket这样看起来与流行的C语言截然不同的语言。但是,给定语言的语法和特定功能仍然只是掌握该语言或任何其他语言的一般编程所需知识的一小部分。事实上,经验丰富的程序员学习一门新语言的速度比新手快得多,因为他们已经知道构成新语言基础的大部分概念,所以他们可以专注于学习语法或学习特定于语言的功能。

当Racket程序员第一次学习JavaScript时,他们已经知道如何使用匿名函数、闭包和高阶函数,所以他们只需要将现有知识映射到JavaScript';并关注其独特的功能。这种优势只会随着时间的推移而增加,因为一个人学习的语言越多,他在学习一门新语言时需要利用的知识基础就越多。

因此,是的,你将不得不投入时间学习Racket的细节,但这一努力的回报是,当你转到其他语言时,所获得的大部分知识将产生回报。而且,由于Racket的核心非常小,表达能力也非常强,因此它允许用户快速深入到编程中那些更困难但最终更有价值的方面,而不是陷入Java等语言中繁重的语法和古怪之处。

函数式编程的好处通常是从智力的角度来赞美的。故事是这样的:函数式编程#39;s对纯粹性和无状态性的关注将继续下去,以便在不强调这些方面的命令式编程语言中产生更好的结果。我同意这种观点,因为我觉得在任何语言中,近乎函数式的编程风格都是一种有用的默认设置,而当它们';我们需要。

尽管如此,即使工业界的绝大多数程序员和工程师使用非函数式语言,说函数式编程本身在实践中完全无用也是不准确的。

例如,今年夏天,我将在一家在后端使用Clojure的公司担任软件工程师实习生,这是一个只有在Racket方面有经验的机会。我这样说不是为了吹嘘,而是为了指出,如果你想专业地使用函数式编程,即使你的简历上只有Racket一种语言,你也可以勉强应付。

函数式编程社区当然比软件工程中的其他社区小,但这';这不完全是件坏事。根据我(尽管有限)的经验,与吸引大量申请的旗舰工作或实习程序员相比,较小的机会往往更容易获得,也更有回报。最后,每个申请者都需要一些能让他们脱颖而出的东西,如果函数式编程是他们的激情所在,那可能就是。

此外,也许更重要的是,即使软件工程中的函数式编程社区相对较小,但函数式工具和技术在几乎所有现代语言中的重要性最近都有了相当大的增长。

在过去几年中,Java和C#等面向对象编程的堡垒增加了对lambda表达式的支持,Python引入了结构模式匹配——这两种重要工具都是从函数式语言中衍生出来的。

不仅如此,React及其使用钩子管理状态的功能性方法已经成为前端web开发社区中事实上的JavaScript UI库。即使函数式编程语言在采用上落后于命令式或面向对象的语言,它们的技术和特性也越来越丰富,以至于它们';你现在不可避免了。

最重要的是,Racket是一种优秀的编程语言,在我看来,对于新手和经验丰富的程序员来说都是一种很有价值的学习工具。那';这就是为什么当我的同龄人因为我认为是非理性的原因而抱怨这种语言时,我会感到沮丧。不是每个人都需要喜欢或更喜欢Racket中的函数式编程,但我希望这篇文章至少能帮助其中一些人欣赏它的价值。