• 泛函编程(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会提示。

  • 相关阅读:
    JAVA变量的作用域
    SQLite
    ajax
    浏览器调试
    SQL链接
    Computer
    Sql知识点总结
    Web Socket
    秒杀
    副业
  • 原文地址:https://www.cnblogs.com/tiger-xc/p/4331449.html
Copyright © 2020-2023  润新知