函数式编程核心概念拆解:用Haskell和Scala讲透本质

纯函数:函数式编程的“原子”

函数式编程的第一原则,是纯函数——它像数学里的函数一样“诚实”:输入确定,输出就确定,没有任何隐藏的副作用(比如修改全局变量、打印日志、操作文件)。

函数式编程核心概念拆解:用Haskell和Scala讲透本质

举个Haskell的例子:

-- 纯函数:计算一个数的平方
square :: Int -> Int
square x = x * x  -- 输入5,一定输出25;输入10,一定输出100

再看Scala的纯函数:

// 纯函数:计算两个数的和
def add(a: Int, b: Int): Int = a + b  -- 不管调用多少次1+2永远是3

反过来,非纯函数就像“会偷偷搞事情”的函数。比如Haskell里依赖全局变量的函数:

-- 非纯函数:依赖全局计数器
counter :: Int
counter = 0

increment :: Int -> Int
increment x = x + counter  -- 若counter被修改,结果会变!比如counter变成1,输入5会输出6

Scala里修改外部变量的函数更直观:

var globalCount = 0  -- 可变的全局变量
def impureIncrement(a: Int): Int = {
  globalCount += a  -- 修改了外部状态这就是副作用
  globalCount
}

为什么纯函数重要?因为它可测试(不用 mock 外部状态)、可复用(哪里需要哪里搬)、可并行(没有竞态条件)。你有没有遇到过“改了一行代码,整个系统崩了”的情况?大概率是因为用了非纯函数——纯函数能帮你把这种“意外”降到最低。

不可变数据:告别“意外修改”的安全感

函数式编程里还有个“反直觉”的规则:数据一旦创建,就不能修改。Haskell从语言层面强制不可变,Scala则给了你选择(用val定义不可变变量,var定义可变变量,但函数式风格里尽量不用var)。

先看Haskell的不可变列表:

originalList :: [Int]
originalList = [1,2,3]  -- 创建后永远是[1,2,3]

newList :: [Int]
newList = 0 : originalList  -- 生成新列表[0,1,2,3],originalList没变化

Haskell里没有“修改列表某个元素”的操作,所有“修改”都是生成新数据。

Scala里的不可变数据更灵活,比如case class默认不可变:

// 不可变的用户类型
case class User(name: String, age: Int)
val alice = User("Alice", 25)  -- 用val定义alice的name和age永远不能改
// alice.age = 26  -- 编译错误!想改?生成新对象:
val olderAlice = alice.copy(age = 26)  -- 新对象旧对象不变

不可变数据的好处是什么?线程安全——多线程环境下不用加锁,因为没人能修改数据;可追溯——数据变化的每一步都有记录(比如originalListnewList),调试时能快速定位问题。你有没有遇到过“不知道谁改了我的变量”的崩溃?不可变数据能直接解决这个痛点。

高阶函数:让函数变“积木”

函数式编程里,函数是“一等公民”——它能像变量一样被传递、被返回、被作为参数。而接受函数作为参数,或返回函数的函数,就是高阶函数

最常见的高阶函数是map(遍历数据并转换)和filter(筛选数据)。先看Haskell的map

-- 用map把列表里的每个元素乘以2
doubleList :: [Int] -> [Int]
doubleList = map (*2)  -- (*2)是一个“乘以2”的函数,作为map的参数

-- 调用:doubleList [1,2,3] → [2,4,6]

Scala的map用法几乎一样,但更贴近OOP风格:

// Scala的List是不可变的,map返回新列表
val numbers = List(1,2,3)
val doubled = numbers.map(_ * 2)  -- _*2是匿名函数等价于x => x*2结果[2,4,6]

再看更复杂的高阶函数——Haskell的foldl(折叠列表计算总和):

-- 用foldl计算列表的和
sumList :: [Int] -> Int
sumList = foldl (+) 0  -- (+)是加法函数,0是初始值
-- 调用:sumList [1,2,3] → 6

Scala里对应的是foldLeft

val sum = numbers.foldLeft(0)(_ + _)  -- 初始值0累加每个元素结果6

高阶函数的核心价值是抽象重复逻辑。比如你要写“把列表里的每个元素转成字符串”“把列表里的每个元素加1”——不用写两个循环,用map就能搞定。想象一下:函数变成了“积木”,你可以用不同的“积木”(函数)组合出不同的功能,这比写一堆重复代码爽多了!

延迟计算:按需执行的效率魔法

延迟计算(也叫惰性求值)是函数式编程的“效率神器”——它不会立即计算结果,而是等到真正需要的时候才执行。Haskell默认是延迟计算,Scala则用LazyList实现(之前叫Stream)。

先看Haskell的无限列表:

-- 生成从1开始的无限列表
infiniteList :: [Int]
infiniteList = [1..]  -- 理论上无限,但不会立即生成所有元素

-- 取前5个元素,此时才会计算
take5 :: [Int]
take5 = take 5 infiniteList  -- 结果[1,2,3,4,5]

如果是 imperative 语言(比如Java),生成无限列表会直接内存溢出,但Haskell因为延迟计算,只计算需要的部分。

Scala的LazyList用法类似:

// 生成无限序列
val lazyInfinite = LazyList.from(1)  -- 不会立即生成元素
val take5Scala = lazyInfinite.take(5).toList  -- 按需计算前5个结果List(1,2,3,4,5)

延迟计算的好处是什么?处理大数据或无限数据时省内存。比如你要读取一个10GB的日志文件,不用一次性加载到内存,用延迟计算可以“读一行处理一行”;再比如生成斐波那契数列,不用预先计算所有项,需要多少取多少。

模式匹配:比switch更聪明的分支处理

函数式编程里的模式匹配,是比switch/if-else更强大的分支处理工具——它能匹配数据的结构(比如列表的头和尾、case class的字段),还能自动检查“是否覆盖所有情况”。

先看Haskell的列表模式匹配:

-- 计算列表的长度(递归实现)
listLength :: [a] -> Int
listLength list = case list of
  [] -> 0  -- 空列表,长度0
  x:xs -> 1 + listLength xs  -- 非空列表,取头x,尾xs,长度+1

这里的x:xs是Haskell的列表模式——x是列表的第一个元素,xs是剩下的元素。

Scala的模式匹配更灵活,能匹配case class

// 定义一个表示形状的case class
sealed trait Shape  -- 密封特质所有子类必须在同一个文件里
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
case class Triangle(base: Double, height: Double) extends Shape

// 用模式匹配计算面积
def calculateArea(shape: Shape): Double = shape match {
  case Circle(r) => math.Pi * r * r  -- 匹配Circle取radius为r
  case Rectangle(w, h) => w * h  -- 匹配Rectangle取width和height
  case Triangle(b, h) => 0.5 * b * h  -- 匹配Triangle取base和height
}

模式匹配的优势在哪里?更简洁(不用写一堆getter)、更安全(密封特质会检查是否覆盖所有子类,避免遗漏)。比如Scala里如果漏了Triangle的情况,编译时会报错——这比switch的“漏了case也不提醒”贴心多了!

从概念到实践:为什么要学函数式编程?

看到这里,你可能会问:“这些概念听起来不错,但实际开发中有用吗?”

比如在Scala中,Spark(大数据框架)的核心API就是函数式风格——mapfilterreduce这些高阶函数,正是处理大数据的基础;Haskell则在编译器、金融系统(需要高精度计算)中广泛应用,因为纯函数和不可变数据能保证计算的正确性。

更重要的是,函数式编程能改变你的思维方式——从“怎么修改数据”变成“怎么组合函数”,从“解决具体问题”变成“抽象通用逻辑”。这种思维升级,会让你写出更简洁、更可靠、更易维护的代码。

原创文章,作者:,如若转载,请注明出处:https://zube.cn/archives/333

(0)