F#函数式编程教学方法研究
来源:用户上传
作者:
摘要:函数式编程语言及函数特性在工业界逐渐流行,函数式编程语言教学具有重要的理论与现实意义。以提高教学质量为目的,通过仔细分析默认不可变、高阶函数、模式匹配、数据与函数解耦等编程语言特征,采用讨论对比与实践方法,研究函数式编程语言教学方法,对学生编程思维进行训练,并对未来编程技术发展趋势进行分析。采用实例编程教学与不同语言对比相结合的方法,使《函数式编程语言》教学质量得到有效提升。
关键词:F#;函数式编程;编程实践;教学特点;教学內容组织
DOI:10.11907/ejdk.191325开放科学(资源服务)标识码(OSID):
中图分类号:G433文献标识码:A 文章编号:1672-7800(2019)010-0201-03
0引言
函数式编程范式,解耦了数据和处理数据的函数,将数据在不同处理函数之间的流动过程展现给用户,使用户能够对自己的业务逻辑始终保持专注,避免了命令式编程不断向计算机描述如何完成某项工作的琐碎步骤,也避免了面向对象抽象過程中数据和方法的耦合以及类继承的复杂性。函数式编程与命令式编程范式相比,具有数学上的优雅性。因为计算机编程过程处理的对象是数据,函数才是处理不同有组织数据的核心。在初步掌握数据结构后,学生可通过函数式编程语言了解利用计算机解决计算问题的思想,进而将这种思想应用于计算机编程实践。
F#是微软.NET开发平台的一门编程语言,由微软伦敦研究院研发,目前是全平台的开源编程语言,技术演进完全由社区驱动,其最鲜明的特点是对函数式编程范式(FP,FunctionalProgramming)的引入,同时F#对面向对象(OOP)编程的支持也同样出色。使用F#语言,开发人员可以自由选择函数式编程或面向对象编程实现目标。此外,F#还可与NET平台上的C#、VB等编程语言紧密结合,通过相互调用完成历史代码复用,实现更大规模的项目。
函数式编程思想与传统的C语言、较新的Python、Go语言不同,具有一定新颖性,带来解决问题的新思路,尤其是对于并行与异步编程,相比Java等编程语言,更加利于学生理解问题的关键和核心。近年函数式编程语言在工业界流行,在大学开设相关课程是教改关注的热门话题。这种范式的编程语言与大众熟知的面向对象、命令式编程语言不同,为编程人员提供了新的问题解决思路与思维工具。尽管这种编程语言在工业界和学术圈流行,但尚未在国内高校编程教学中得到重视。
本文从函数式编程的新视角出发,研究了函数式编程特点,用不同的方式解决传统软件开发的诸多问题。启发学生思维,为解决老问题提供新思路。
1默认不可变
函数式编程语言强调量默认的不可变性,如C语言中,入门时一定会告诉学生需要定义一个整形变量sum,初始值为0,当对1-100的整形数求和时,可以不断将1-100中的数字i加到sum中,改变sum变量的值为旧的sum加上新的i,这样可以求得最终的和值。在这个过程中,编程者向计算机仔细描述了实现一个数列求和功能的函数。
而在函数式编程中,因为量默认是不可变的,就不能进行累计的求和累加行为,需要换一种思路。从代码1可以看到,1~4行构造了一个递归函数,这个递归函数有两个输入:第一个输入data是一个要求和的list,第二个输入是用来求和的sum。接下来的match with关键字是现代编程语言,诸如Scala、Swift等编程语言,都有类似的语法特征(如用Switch实现这一功能)。可以看到“0”竖线部分代表一种需要被匹配的情况,如果匹配成功,则执行箭头指向的语句块。匹配规则是如果data的list由一个头元素head和若干尾元素tail(这里的tail是一个去掉data头元素的list)组成,那么函数会将头元素加到sum上,得到新的sum,并对tail尾进行上述操作,不同的是输入给getSum函数的sun值已经变成在原来的sum值上加了head值。在第5行中,当data的list为空时,可以看到getSum函数会将sum输入的结果直接返回给用户。在代码1第6行中,可以看到一个求1到100的和的例子,data输入对应[1…100],代表一个从1到100,步长为1的list,sum输入一个整形0作为和的初始值。
代码1:基于函数式编程语言的不可变、模式匹配特性进行求和。
从这段代码可以看出,编程人员在实现过程中没有用到任何可变变量,一切值在所有行里都是不可变的,这保证了当代码在多核或多线程中运行时没有任何副作用,是纯粹(pure)的。
量默认不可变优势非常明显,任何一段代码拿到一个量都可以放心使用这个量,不必担心代码在其它线程或内核中改变这个值,这在机制上克服了副作用,避免了加锁等一系列繁琐操作。
2高阶函数与声明式编程
分析例证1:求一个list的和,可以给一个初始值,将list中每个头元素不断递归与初始值求和,并作为新的初始值送人下一次递归,直到list为空,即可求得整个list的和值,这其实就是MAP/REDUCE计算模型中的REDUCE过程。
代码2:描述做什么(WHAT),而不是诉说怎么做(WHAT)。
分析例证2:通过一个计算实例向学生展现函数编程语言的优雅与简洁。当存在一个list,从1到100,要求对其中的每一个元素做某种运算f生成一组新的元素,再对其中符合某种条件t的元素进行保留,最后对剩余元素求和。
通过提问方式讨论传统编程语言解决这一问题的方法。通常发现需要对list元素进行遍历,计算遍历元素i在厂函数作用下的结果f(i),然后判断f(i)是否符合条件t。如符合,将其值加入一个累计变量sum中作为最终的和;如不符合t则不进行任何操作。而在F#代码2中可以看出,第1行构造目标数据,第2行对数据中每一个元素进行操作(将每个元素平方,然后加1),第3行对新生成数据进行过滤,保留其中可以被2整除的元素,第4行对结果进行求和。第2行和第3行中,直接将一个需要进行的操作和判断的条件放在map函数及filter函数后面,对需要进行操作或过滤的数据进行操作或过滤,这样可以接受一个作为输入的函数,叫做高阶函数。学生在学习到这一特性时,普遍会觉得函数式编程的语法更容易理解,同时看起来很优雅。 有了高阶函数,函数式编程赋予编程者组合拼接各种功能函数的能力,从而通过若干小的功能就可构造出更大更多功能。而高阶函数直接避免了指针(或代理)的使用,可以将函数很轻松地作为像量一样的东西传递,这种将函数和量保持在同等地位的特性也是函数式编程独有的。
3并行计算与异步计算
随着单纯提高CPU频率满足摩尔定律的终结,中央处理器的核心数量越来越多,并行并发计算在各种编程语言中都是学习难点。而F#的语法设计在学习并行与异步计算课程中异常简单。示例代码如代码3、代码4所示。
代码3:數组同步并行计算代码示例。
代码3与代码2的运算完全一致,不同的是代码2的计算由单个CPU核心完成,代码3则会使用CPU所有的逻辑核心和实体核心。仔细比较两段代码,可发现它们高度类似,只是list换成了array及额外增加了一个parallel。之所以将list换成array,是因为list对模式匹配的支持更丰富,但array通过下属parallel模块支持同步并行计算。如示例所示,当对一个集合中每个元素作某种变换(用一个函数处理元素)时,可以直接使用并行计算,这样F#编程中的同步并行计算就可轻易达成。讲授到这里时,学生会感叹函数式编程的易用性。但还需补充讲解list和array数据类型的特点和区别,避免学生在学习中混淆。
代码4:数组异步并行计算代码示例。
同步并行问题在于当计算量较大时,CPU因所有核心都在执行目标操作,无暇顾及其它系统请求,会给用户造成卡顿的感觉。当希望CPU进行计算的同时还可响应必要的系统请求,就需要异步并行计算。还是同样的计算任务,代码4展示了在F#中使用async关键字构造一个异步计算并完成的过程。
第3到第5行的代码将原本要进行的计算包裹在一个结构async{return something}中,代表一个要进行的异步计算,something是这个异步要返回的结果。构造完这个异步计算,第2到第6行就将1~100的100个元素映射成了100个异步计算,第7行的Async.Parallel用fork-join模式,将100个异步计算合并成一个异步计算,在第8行代码的执行命令下,返回这个异步计算结果(注意这时结果是一个数据Array)。
讲授该知识点时,可请学生在课堂上讨论各种编程语言是如何实现异步编程的。在向大家介绍F#的异步实现方式后,通过对比,学生会发现F#不需要构造线程、设计Runnable类对象、声明回调函数等琐碎细节就可直接构造一个异步运算,并可执行和获得结果。
4结语
治学当知行合一(王守仁),学习计算机相关知识尤其需要学生动手实践。当学生面对并行、异步、高阶函数、默认不可变时,这些概念对他们来说是陌生的。通过代码示例、课堂讨论、动手实践,学生能掌握函数式编程的核心概念和解决问题的一般思路。声明式编程将编程人员的焦点从告诉计算机如何做转移到告诉计算机做什么上。计算机编程已经到了一个需要进一步向前演化的时间节点,Scala、F#等函数式编程语言和函数式语法已在工业界逐步流行,这要求学校具有前瞻性,为学生储备相关知识和基础技能。
通过学习《F#函数式编程》课程,学生可理解面向对象编程之外的另一种编程思想,为其将来工作中解决生产实践问题提供新的思维工具。通过不断探索不同知识点的教学方法,学生更容易理解一些全新概念,丰富知识储备和思维方式,对培养新时代的计算机专业人才具有重要意义,计算机编程教学质量也将得到进一步提升。
转载注明来源:https://www.xzbu.com/8/view-15058554.htm