Scala 纯函数是什么?怎么在 Scala 中定义和使用纯函数?

文章导读
Previous Quiz Next 本章将带您了解 Scala 编程中的纯函数概念。纯函数是函数式编程中的基本概念。这些函数由于没有副作用而具有可预测性和可靠性。这些函数对于相同的输入总是返回相同的输出。
📋 目录
  1. 纯函数
  2. 纯函数示例
  3. 纯函数的优势
  4. 纯函数与不可变性
  5. 组合纯函数
  6. 高阶纯函数
  7. 纯函数与递归
  8. 纯函数和 Memoization
  9. Scala 中 Pure 和 Impure Functions 的区别
  10. Pure Function 总结
A A

Scala - 纯函数



Previous
Quiz
Next

本章将带您了解 Scala 编程中的纯函数概念。纯函数是函数式编程中的基本概念。这些函数由于没有副作用而具有可预测性和可靠性。这些函数对于相同的输入总是返回相同的输出。

纯函数

纯函数是指对于相同的输入总是返回相同输出的函数。纯函数是函数式编程的基础,因为它们具有可预测性和可测试性。

纯函数是满足以下条件的函数 -

  • 它对于相同的输入总是给出相同的结果。
  • 它没有副作用。它不会改变任何状态,也不会与外部世界交互(例如,没有 I/O 操作,没有修改全局变量)。

语法

Scala 中纯函数的语法为 -

def functionName(params: Type): ReturnType = {
   // function body with no side effects
   // 无副作用的函数体
}

纯函数示例

以下示例展示了在 Scala 编程中定义和使用纯函数 -

object Demo {
   def add(a: Int, b: Int): Int = {
      a + b
   }

   def main(args: Array[String]): Unit = {
      println(add(3, 4)) 
      println(add(3, 4)) 
   }
}

将上述程序保存为 Demo.scala。使用以下命令编译和执行该程序。

命令

> scalac Demo.scala
> scala Demo

输出

7
7

在示例中,add 函数是纯函数,因为它对于相同的输入总是产生相同的输出。它没有任何副作用。

纯函数的优势

纯函数有多种优势。纯函数是可预测的,因为它们对于相同的输入总是返回相同的结果。因此,调试和测试更容易。这些函数更容易测试,因为它们不依赖任何外部状态。它不会引起副作用。

纯函数与不可变性

您可以在纯函数中使用不可变数据结构来避免副作用。一旦创建值,不可变数据就不能被修改。

语法

使用纯函数结合不可变数据结构的语法为 -

def functionName(params: ImmutableType): ReturnType = {
   // function body working with immutable data
   // 使用不可变数据的函数体
}

示例

考虑在 Scala 编程中使用纯函数结合不可变数据结构的示例 -

object Demo {
   def addToList(list: List[Int], element: Int): List[Int] = {
      list :+ element
   }

   def main(args: Array[String]): Unit = {
      val originalList = List(1, 2, 3)
      val newList = addToList(originalList, 4)
      println(newList) 
      println(originalList) 
   }
} 

将上述程序保存为 Demo.scala。使用以下命令编译和执行该程序。

命令

> scalac Demo.scala
> scala Demo

输出

List(1, 2, 3, 4)
List(1, 2, 3)

在示例中,addToList 函数是纯函数并使用不可变数据工作。因此,原始列表保持不变。但是,addToList 函数返回了一个新列表。

组合纯函数

您可以将纯函数组合起来执行更复杂的操作。这是函数式编程的一个关键优势。

语法

组合纯函数的语法如下 -

def function1(params: Type): ReturnType = {
   // function body
}

def function2(params: Type): ReturnType = {
   // function body calling function1
}

示例

考虑在 Scala 编程中组合纯函数的示例 -

object Demo {
   def square(x: Int): Int = x * x

   def sumOfSquares(a: Int, b: Int): Int = {
      square(a) + square(b)
   }

   def main(args: Array[String]): Unit = {
      println(sumOfSquares(3, 4)) 
      println(sumOfSquares(5, 6)) 
   }
}

将上述程序保存为 Demo.scala。使用以下命令编译并执行该程序。

命令

> scalac Demo.scala
> scala Demo

输出

25
61

在示例中,square 和 sumOfSquares 函数都是纯函数,它们被组合起来执行更复杂的计算。

高阶纯函数

高阶函数要么将其他函数作为参数,要么将函数作为返回值,或者两者兼具。纯高阶函数可以增强代码的模块化和可重用性。

语法

高阶纯函数的语法如下 -

def higherOrderFunction(f: Type => ReturnType, params: Type): ReturnType = {
   // function body calling f
}

示例

考虑在 Scala 编程中高阶纯函数的示例 -

object Demo {
   def applyOperation(f: Int => Int, x: Int): Int = {
      f(x)
   }

   def increment(x: Int): Int = x + 1

   def main(args: Array[String]): Unit = {
      println(applyOperation(increment, 5)) 
      println(applyOperation(increment, 10))
   }
}

将上述程序保存为 Demo.scala。使用以下命令编译并执行该程序。

命令

> scalac Demo.scala
> scala Demo

输出

6
11

在示例中,applyOperation 函数是一个高阶纯函数。它将另一个函数作为参数,并将其应用到一个给定的值上。

纯函数与递归

您可以在递归算法中使用纯函数进行复杂计算,而不会产生副作用。

语法

递归纯函数的语法如下 -

def recursiveFunction(params: Type): ReturnType = {
   // base case
   if (condition) baseResult
   else {
      // recursive case
      recursiveFunction(newParams)
   }
}

示例

考虑在 Scala 编程中递归纯函数的示例 -

object Demo {
   def factorial(n: Int): Int = {
      if (n <= 1) 1
      else n * factorial(n - 1)
   }

   def main(args: Array[String]): Unit = {
      println(factorial(5)) 
      println(factorial(6)) 
   }
}

将上述程序保存为 Demo.scala。使用以下命令编译并执行该程序。

命令

> scalac Demo.scala
> scala Demo

输出

120
720

在示例中,factorial 函数是一个纯递归函数,用于计算给定数字的阶乘。

纯函数和 Memoization

Memoization 是一种优化技术。它会缓存昂贵的 function 调用结果,并在再次遇到相同输入时返回缓存结果。Pure functions 是 memoization 的理想候选,因为它们总是为相同的输入产生相同的输出。

语法

memoizing 一个 pure function 的语法如下 -

def memoize[A, B](f: A => B): A => B = {
   val cache = scala.collection.mutable.Map[A, B]()
   (x: A) => cache.getOrElseUpdate(x, f(x))
}

示例

考虑在 Scala 编程中 memoizing 一个 pure function 的示例 -

object Demo {
   def fibonacci(n: Int): Int = {
      if (n <= 1) n
         else fibonacci(n - 1) + fibonacci(n - 2)
   }

   def memoize[A, B](f: A => B): A => B = {
      val cache = scala.collection.mutable.Map[A, B]()
      (x: A) => cache.getOrElseUpdate(x, f(x))
   }

   def main(args: Array[String]): Unit = {
      val memoizedFibonacci = memoize(fibonacci)
      println(memoizedFibonacci(10))  // 输出: 55
      println(memoizedFibonacci(10))  // 输出: 55 (已缓存)
   }
}

将上述程序保存为 Demo.scala。使用以下命令编译和执行该程序。

命令

> scalac Demo.scala
> scala Demo

输出

55
55

在示例中,fibonacci function 被 memoized 以优化重复计算。因此它是 pure 的,并产生一致的结果。

Scala 中 Pure 和 Impure Functions 的区别

这是 Scala 中 pure 和 impure functions 的比较 -

特性 Pure Function Impure Function
定义 总是为相同的输入返回相同的输出。 可能为相同的输入返回不同的输出。
副作用 没有副作用。 可能有副作用(例如,修改全局变量、执行 I/O 操作)。
状态修改 不修改任何状态。 可以修改状态。
测试 由于可预测性和无依赖关系,更容易测试。 由于潜在的副作用和对外部状态或 I/O 操作的依赖,更难测试。
示例
def add(a: Int, b: Int): Int = a + b
def printSum(a: Int, b: Int): Unit = println(a + b)

Scala 中 pure functions 的示例有:abs、ceil、max 等。Scala 中 impure functions 的示例有:isEmpty、length、substring 等。

Pure Function 总结

  • Scala 中的 pure functions 总是为相同的输入产生相同的输出。pure functions 没有副作用。
  • 它们提供了可预测性、引用透明性、易于测试等特性。
  • Pure functions 与 immutable data structures 配合良好,可以组合创建复杂操作。
  • 高阶 pure functions 提供了代码模块化和可重用性。
  • 你可以在 recursive algorithms 中使用 pure functions。这些非常适合 memoization 以优化性能。