泛函编程(2)-初次体验泛函编程

简介:

  泛函编程和数学方程式解题相似;用某种方式找出问题的答案。泛函编程通用的方式包括了模式匹配(pattern matching)以及递归思维(Recursive thinking)。我们先体验一下:(在阅读本系列博客文章之前,相信读者已经对Scala语言及REPL用法有所了解了。在这就不去解释Scala的语法语意了。)

先来个简单的:


1 def reportError(msgId: Int): String = msgId match {
2      | case 1 => "Error number 1."
3      | case 2 => "Error number 2."
4      | case 3 => "Error number 3."
5      | case _ => "Unknown error!"
6      | }
7 reportError: (msgId: Int)String

很明显,这个函数的是一个纯函数,也是一个完整函数。因为函数主体涵盖了所有输入值(注意: case _ =>)。我们可以预知任何输入msgId值所产生的结果。还有,函数中没有使用任何中间变量。看看引用情况:


1 reportError(2)
2 res3: String = Error number 2.
3 
4 scala> reportError(-1)
5 res4: String = Unknown error!

恰如我们预测的结果。

再来看看一个递归(Recursion)例子:阶乘(Factorial)是一个经典样例:


1 def factorial(n: Int): Int = {
2       if ( n == 1) n
3       else n * factorial(n-1)
4   }                                               //> factorial: (n: Int)Int
5   factorial(4)                                    //> res48: Int = 24

也可以用模式匹配方式:


1 def factorial_1(n: Int): Int = n match {
2       case 1 => 1
3       case k => k * factorial(n-1)
4   }                                               //> factorial_1: (n: Int)Int
5   factorial_1(4)                                  //> res49: Int = 24

用模式匹配方式使函数意思表达更简洁、明了。

我们试着用“等量替换”方式逐步进行约化(reduce)


1 factorial(4)
2   4 * factorial(3)
3   4 * (3 * factorial(2))
4   4 * (3 * (2 * factorial(1)))
5   4 * (3 * (2 * 1)) = 24

可以得出预料的答案。

递归程序可以用 loop来实现。主要目的是防止堆栈溢出(stack overflow)。不过这并不妨碍我们用递归思维去解决问题。 阶乘用while loop来写:


1 def factorial_2(n: Int): Int = {
2          var k: Int = n
3          var acc: Int = 1
4          while (k > 1) { acc = acc * k; k = k -1}
5          acc
6   }                                               //> factorial_2: (n: Int)Int
7   factorial_2(4)                                  //> res50: Int = 24

注意factorial_2使用了本地变量k,acc。虽然从表达形式上失去了泛函编程的优雅,但除了可以解决堆栈溢出问题外,运行效率也比递归方式优化。但这并不意味着完全违背了“不可改变性”(Immutability)。因为变量是锁定在函数内部的。

最后,也可用tail recursion方式编写阶乘。让编译器(compiler)把程序优化成改变成 loop 款式:


1 def factorial_3(n: Int): Int = {
2     @annotation.tailrec
3       def go(n: Int, acc: Int): Int = n match {
4           case 1 => acc
5           case k => go(n-1,acc * k)
6       }
7       go(n,1)
8   }                                               //> factorial_3: (n: Int)Int
9   factorial_3(4)                                  //> res51: Int = 24

得出的同样是正确的答案。这段程序中使用了@annotation.tailrec。如果被标准的函数不符合tail recusion的要求,compiler会提示。




相关文章
|
1月前
|
存储 JavaScript IDE
探索变量世界的奥秘:全面理解与有效避坑
【4月更文挑战第2天】探索编程基础:变量。本文详述变量的定义、作用,如数据存储、信息传递,以及声明与赋值。讨论变量类型(如整型、浮点型)和作用域(全局、局部),并列举常见错误及防范策略,如未声明使用、类型不匹配。最后提出最佳实践,如明确命名、避免冗余和适时复用变量,以提升代码质量。通过本文,深化你对变量的理解,让编程更加得心应手!
40 8
|
3月前
|
存储 Web App开发 运维
发布、部署,傻傻分不清楚?从概念到实际场景,再到工具应用,一篇文章让你彻底搞清楚
部署和发布是软件工程中经常互换使用的两个术语,甚至感觉是等价的。然而,它们是不同的! • 部署是将软件从一个受控环境转移到另一个受控环境,它的目的是将软件从开发状态转化为生产状态,使得软件可以为用户提供服务。 • 发布是将软件推向用户的过程,应用程序需要多次更新、安全补丁和代码更改,跨平台和环境部署需要对版本进行适当的管理,有一定的计划性和管控因素。
172 1
|
1月前
|
算法 程序员
代码与禅意:编程中的心流体验
【4月更文挑战第10天】在编码的世界中,技术感悟往往与禅宗哲学不谋而合。本文探索了编程时的心流状态——一种既集中又放松的创造性境界,它如何与禅宗中追求的“在动中寻静”相呼应。通过深入分析编程过程中的心流体验,我们揭示了如何在逻辑严谨与创造力之间找到平衡点,进而提升编程效率和内在满足感。
|
2月前
|
JavaScript 前端开发 Java
打造高效对象:编程秘籍与代码实操
打造高效对象:编程秘籍与代码实操
9 0
|
6月前
|
PHP
渐进式编程之旅:探寻PHP函数的奇妙世界
欢迎来到渐进式编程之旅!在编程的世界中,函数是一种强大而重要的工具,而PHP函数更是让我们能够创造出令人惊叹的网络应用程序和网站的关键。你是否曾经想过探寻PHP函数的奇妙世界,深入了解它们的工作原理以及如何利用它们来解决编程难题?本文将带你踏上这个神奇之旅,一起揭开PHP函数的面纱,探索这个渐进式编程世界的无限可能性。
103 0
|
7月前
|
存储 算法 C语言
《信任的进化》游戏简易版逻辑算法的实现(C语言)
《信任的进化》游戏简易版逻辑算法的实现(C语言)
|
10月前
|
Java 编译器 索引
Java语法糖:甜化你的编程体验
Java语法糖:甜化你的编程体验
Java语法糖:甜化你的编程体验
|
11月前
|
C语言
这个小游戏你肯定玩过,但是如果你能用C语言自己写出来,那是不是体验感更好呢?看完我这篇文章,我保证你能写出来
这个小游戏你肯定玩过,但是如果你能用C语言自己写出来,那是不是体验感更好呢?看完我这篇文章,我保证你能写出来
|
12月前
|
小程序 开发工具
彻底搞清微信小游戏开发中的循环的使用
本文主要内容为游戏开发中常用的循环的使用方法。 如果你没有任何的游戏开发经验,欢迎阅读我的“人人都能做游戏”系列教程,它会手把手的教你做出自己的第一个小游戏。
128 0
|
12月前
|
人工智能 监控 自动驾驶
在生活中怎么使用AIGC
在生活中怎么使用AIGC
211 38