深入理解Scala高阶函数:函数式编程核心

很多人第一次听到"高阶函数"这个词,觉得很高级。

其实定义特别简单:接受函数作为参数,或者返回函数作为结果的函数,就叫高阶函数。

就这么简单。

Java里你写过Collections.sort(list, comparator)吧?这个sort方法接受一个Comparator对象作为参数------Comparator里面封装的是比较逻辑。这其实就是高阶函数的思想------只不过Java用对象来封装逻辑,Scala直接用函数。

java 复制代码
// Java:用Comparator对象封装比较逻辑
Collections.sort(users, new Comparator<User>() {
  @Override
  public int compare(User u1, User u2) {
    return u1.getAge() - u2.getAge();
  }
});
scala 复制代码
// Scala:直接传函数
users.sortBy(_.age)
users.sortWith(_.age < _.age)

这篇文章不讲"高阶函数是什么"这种入门废话,直接说干货:Scala的高阶函数底层是什么?为什么map/filter/reduce都是高阶函数?函数返回函数到底有什么用?


先从一个痛点说起

Java里你想对集合做操作,怎么做?------写for循环。

java 复制代码
// Java:把每个元素翻倍
List<Integer> doubled = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
  doubled.add(list.get(i) * 2);
}

// 过滤出大于1的元素
List<Integer> filtered = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
  if (list.get(i) > 1) filtered.add(list.get(i));
}

// 求和
int sum = 0;
for (int i = 0; i < list.size(); i++) {
  sum += list.get(i);
}

三个操作,三段for循环。你发现了吗?------这三段for循环的结构一模一样,只有中间那一步不一样。

翻倍的那一步是x * 2,过滤的那一步是x > 1,求和的那一步是x + y。

既然结构一样,为什么不把这个结构抽出来,只让调用方传"那一步不一样的逻辑"?

这就是高阶函数。

scala 复制代码
// Scala:把结构抽出来,只传不一样的逻辑
list.map(_ * 2)        // 翻倍------传一个"每个元素怎么变"的函数
list.filter(_ > 1)     // 过滤------传一个"每个元素留不留"的函数
list.reduce(_ + _)     // 求和------传一个"两个元素怎么合"的函数

看到了吗?map、filter、reduce------它们都是高阶函数。它们把for循环的结构封装了,只让你传"那一步不一样的逻辑"。

高阶函数的本质:把"怎么做"封装起来,让你只写"做什么"。


高阶函数的两种形式

高阶函数有两种形式------函数作为参数 ,和函数作为返回值。

形式1:函数作为参数

这是最常见的形式------map、filter、reduce都是这种。

scala 复制代码
// 自定义一个高阶函数:接受一个函数作为参数
def applyTwice(f: Int => Int, x: Int): Int = {
  f(f(x))
}

applyTwice(_ + 1, 5)  // 7------先加1再加1
applyTwice(_ * 2, 5)  // 20------先乘2再乘2

applyTwice接受两个参数------一个函数f,一个整数x。它把f应用两次。

这就是函数作为参数------你把"要做什么"作为函数传进去,高阶函数负责"怎么应用这个函数"。

形式2:函数作为返回值

这种形式更强大------函数返回另一个函数。

scala 复制代码
// 函数返回函数:生成一个"加n"的函数
def addN(n: Int): Int => Int = {
  (x: Int) => x + n
}

val addOne = addN(1)
val addTen = addN(10)

addOne(5)   // 6
addTen(5)   // 15

看到了吗?addN返回的是一个函数Int => Int。你传1进去,它返回一个"加1"的函数;你传10进去,它返回一个"加10"的函数。

这就是函数工厂------函数返回函数,让你可以批量生成函数。

和偏应用函数很像------但偏应用函数是"固定已有函数的参数",高阶函数返回函数是"生成全新的函数"。


集合库:高阶函数的主战场

Scala集合库里到处都是高阶函数。你每天写的map、flatMap、filter、reduce、fold、foreach------全是高阶函数。

scala 复制代码
val list = List(1, 2, 3, 4, 5)

// map:每个元素怎么变
list.map(_ * 2)           // List(2, 4, 6, 8, 10)

// filter:每个元素留不留
list.filter(_ % 2 == 0)   // List(2, 4)

// reduce:两个元素怎么合
list.reduce(_ + _)        // 15

// fold:带初始值的合并
list.fold(10)(_ + _)      // 25------10 + 1 + 2 + 3 + 4 + 5

// flatMap:每个元素变成一个序列,再拍平
list.flatMap(x => List(x, x * 10))
// List(1, 10, 2, 20, 3, 30, 4, 40, 5, 50)

// foreach:对每个元素做副作用
list.foreach(println)

为什么Scala的集合操作这么强大?------因为高阶函数让你把"数据怎么变换"抽象成函数,然后传给集合。

Java 8的Stream也有这些------map、filter、reduce。但Java是被迫加的------因为函数式编程的思想太有价值了,不加不行。Scala从第一天就有------因为它本来就是函数式语言。


高阶函数的威力:函数组合

高阶函数最强大的地方不是单个用------而是组合。

scala 复制代码
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// 链式组合:先过滤,再变换,再求和
val result = numbers
  .filter(_ % 2 == 0)     // 偶数:2,4,6,8,10
  .map(_ * 2)              // 翻倍:4,8,12,16,20
  .reduce(_ + _)           // 求和:60

println(result)  // 60

一行链式调用,做了三件事------过滤、变换、聚合。没有for循环,没有中间变量,没有样板代码。

你用Java写一下试试?------三段for循环,三个中间List,十几行代码。

这就是高阶函数组合的威力:把复杂的数据处理,拆成一串简单的函数变换。

每个高阶函数只做一件事------filter只管过滤,map只管变换,reduce只管聚合。你把它们组合起来,就完成了复杂的逻辑。

这就是函数式编程的核心思想:把复杂的计算,拆成简单函数的组合。


几个常见的坑

坑1:函数参数的类型签名

scala 复制代码
// 函数参数的类型:Int => Int 表示一个Int到Int的函数
def apply(f: Int => Int, x: Int): Int = f(x)

// 两个参数的函数:(Int, Int) => Int
def combine(f: (Int, Int) => Int, a: Int, b: Int): Int = f(a, b)

很多人第一次写高阶函数的时候,搞不清函数参数的类型签名。记住:

  • A => B 是一个参数的函数
  • (A, B) => C 是两个参数的函数
  • () => A 是没有参数的函数

坑2:by-name参数和高阶函数

Scala有一个特殊的语法------by-name参数:

scala 复制代码
// 注意:这个不是函数参数,是by-name参数
def whileLoop(condition: => Boolean)(body: => Unit): Unit =
  if (condition) { body; whileLoop(condition)(body) }

condition: => Boolean和condition: () => Boolean不一样------前者是by-name参数,调用的时候直接写condition;后者是函数参数,调用的时候要写condition()。

坑3:序列化问题

和所有函数式特性一样------高阶函数传的函数在底层也是FunctionN实例。在Flink/Spark里,要注意序列化问题。


什么时候用高阶函数?

判断标准很简单:

你是不是有一段逻辑,其中"结构"是固定的,但"具体做什么"是变化的?

如果是------把结构封装成高阶函数,把"具体做什么"作为函数参数传进去。

场景 用高阶函数吗?
集合的map/filter/reduce 用------典型场景
模板方法:固定流程,变化具体步骤 用
回调函数:事件触发后做什么 用
函数工厂:批量生成函数 用
逻辑完全固定,没有变化 不用
简单的一次性操作 不用

简单说:当你想把"怎么做"和"做什么"分开的时候,用高阶函数。


写在最后

我写这篇文章的时候,特意没罗列高阶函数有多少种用法------因为用法很简单,谁都能写。

真正值得花时间理解的是:高阶函数为什么是函数式编程的核心?

因为函数式编程的核心思想就是:把函数当一等公民,用函数来抽象和组合逻辑。

没有高阶函数,函数就不能当参数传------那你就没有map、filter、reduce。没有这些,你就只能写for循环------命令式编程。

有了高阶函数,你可以把逻辑封装成函数,传给其他函数------你可以组合函数、变换函数、生成函数。这就是函数式编程。

Java花了很长时间才意识到这一点------Java 8加了Lambda和Stream。但Java的高阶函数是不完整的------它是"对象封装函数",不是"函数本身"。

Scala从第一天就有完整的高阶函数------因为它是函数式语言,高阶函数就是它的核心。

高阶函数不是一个"高级特性"------它是函数式编程的基石。它让你从"写怎么做"变成"写做什么"------从命令式变成声明式。

你写list.map(_ * 2)的时候,你没有写循环怎么遍历、怎么累加、怎么返回------你只写了"每个元素翻倍"。这就是声明式------告诉计算机做什么,不告诉它怎么做。

这就是高阶函数的价值:把程序员从"怎么做"的细节里解放出来,专注在"做什么"。

相关推荐
czq_26867194876 小时前
Python打卡第46天
开发语言·python
Evand J8 小时前
【MATLAB例程介绍】(17)永磁同步电机(PMSM)基于自适应无迹卡尔曼滤波(AUKF)的电流噪声在线估计。测量噪声协方差自适应与转速估计对比仿真
开发语言·matlab·卡尔曼滤波·无迹卡尔曼滤波·pmsm·电机滤波
BaiPeter10 小时前
ZPL文件预览
开发语言·c++
噢,我明白了15 小时前
JAVA中的一些基础应用知识点
java·开发语言
时间的拾荒人16 小时前
Redis 入门详解:核心特性、重要文件与全局命令实战
开发语言·redis·面试
敲上瘾16 小时前
Qt Widgets入门:项目模板解析与Hello World
开发语言·数据库·c++·qt·ui·qt6.3
码途行者17 小时前
网站的SQL 注入:从攻击原理到防御措施
开发语言·sql·php
旖旎夜光17 小时前
【C++ 基础入门】C++ 多态(中):虚函数表、多态原理与动静绑定
开发语言·c++·笔记·学习
你看我名字18 小时前
产品参数昨天还对,今天已过期:数眼智能与大模型应用如何做资料版本校验
开发语言·架构