• 分类

  • 重置

深入理解Scala函数式编程过程

    深入理解Scala函数式编程过程

    我们马上开始一段变态的过程

    如果要求立方和,可以这么做

    
    35 * 35 * 35 
    68 * 68 * 68 

    没毛病,抽象一点儿,写个函数:

    
    def cube(n: Int) = n * n * n 
    cube(35) 
    cube(68)
    

    省事儿了,如果求1到10的立方和,OK,写个递归

    
    def cube(n: Int) = n * n * n 
     def sumCube(a: Int, b: Int): Int = 
       if (a > b) 0 else cube(a) + sumCube(a + 1, b) 
     
    sumCube(1, 10)
    

    变态一点儿,立方和,平方和,阶乘和,依旧写出它们的函数并且依次计算没毛病

    
    def cube(n: Int) = n * n * n 
    def id(n: Int) = n 
    def square(n : Int) = n * n 
    def fact(n: Int): Int = 
      if (n == 0) 1 else n * fact(n - 1) 
     
    def sumCube(a: Int, b: Int): Int = 
      if (a > b) 0 else cube(a) + sumCube(a + 1, b) 
     
    def sumSquare(a: Int, b: Int): Int = 
      if(a > b) 0 else square(a) + sumSquare(a + 1, b) 
      
    def sumFact(a: Int, b: Int): Int = 
      if (a > b) 0 else fact(a) + sumFact(a + 1, b) 
     
    def sumInt(a: Int, b: Int): Int = 
      if(a > b) 0 else id(a) + sumInt(a + 1, b)  
     
     sumCube(1, 10) 
     sumInt(1, 10) 
     sumSquare(1, 10) 
     sumFact(1, 10)
    

    然后你发现,你已经写了一堆同样逻辑的if else,看起来不奇怪么,这种无脑的操作当然要避免:

    我们要把这些函数名不同但是处理逻辑相同的渣渣都封装到一个函数中,并且这个函数将作为参数赋值到高阶函数中,运行的结果只跟传入的参数类型有关系,也就是把cube,square,fact,泛化成一个f

    
    def cube(n: Int) = n * n * n 
    def id(n: Int) = n 
    def square(n : Int) = n * n 
    def fact(n: Int): Int = 
      if (n == 0) 1 else n * fact(n - 1) 
    //高阶函数
    def sum(f: Int=>Int, a:Int, b:Int): Int = 
      if(a>b) 0 else f(a)+sum(f, a+1, b)
    // 使用高阶函数重新定义求和函数
    def sumCube(a: Int, b: Int): Int = sum(cube, a, b) 
    def sumSquare(a: Int, b: Int): Int = sum(square, a, b) 
    def sumFact(a: Int, b: Int): Int = sum(fact, a, b) 
    def sumInt(a: Int, b: Int): Int = sum(id, a, b) 
     
     sumCube(1, 10) 
     sumInt(1, 10) 
     sumSquare(1, 10) 
     sumFact(1, 10)
    

    但是这样写,还有个问题,就是前面定义了一堆cube,id的初始定义,后面还要继续定义,实际上就是套了一层包装,不要了,去掉,使用匿名函数的功能来将调用进一步简化。多数情况下,我们关心的是高阶函数,而不是作为参数传入的函数,所以为其单独定义一个函数是没有必要的。值得称赞的是 Scala 中定义匿名函数的语法很简单,箭头左边是参数列表,右边是函数体,参数的类型是可省略的,Scala 的类型推测系统会推测出参数的类型。使用匿名函数后,我们的代码变得更简洁了:

    
    //保留逻辑较为复杂的函数
    def fact(n: Int): Int = 
    if (n == 0) 1 else n * fact(n - 1) 
     
    def sum(f: Int => Int, a: Int, b: Int): Int = 
      if (a > b) 0 else f(a) + sum(f, a + 1, b) 
     
    // 使用高阶函数重新定义求和函数
    def sumCube(a: Int, b: Int): Int = sum(x => x * x * x, a, b) 
    def sumSquare(a: Int, b: Int): Int = sum(x => x * x, a, b) 
    def sumFact(a: Int, b: Int): Int = sum(fact, a, b) 
    def sumInt(a: Int, b: Int): Int = sum(x => x, a, b) 
     
    
    sumCube(1, 10) 
    sumInt(1, 10) 
    sumSquare(1, 10) 
    sumFact(1, 10)
    
    

    写到这里问题解决的差不多了,但是我们仔细想想,函数式编程的真谛,一个输入到另一个输出,而不是像这样两个参数传来传去,看起来很麻烦,于是乎

    
    def fact(n: Int): Int = 
    if (n == 0) 1 else n * fact(n - 1) 
     
    // 高阶函数
    def sum(f: Int => Int): (Int, Int) => Int = { 
      def sumF(a: Int, b: Int): Int = 
       if (a > b) 0 else f(a) + sumF(a + 1, b) 
     
      sumF 
    } 
    // 使用高阶函数重新定义求和函数
    def sumCube: Int = sum(x => x * x * x) 
    def sumSquare: Int = sum(x => x * x) 
    def sumFact: Int = sum(fact) 
    def sumInt: Int = sum(x => x) 
     
    // 这些函数使用起来还和原来一样 ! 
    sumCube(1, 10) 
    sumInt(1, 10) 
    sumSquare(1, 10) 
    sumFact(1, 10)
    

    实际上这个时候sum里面传入的已经是匿名函数了,类似于g(f(x))里面的f(x), 你还需要去调用那个f(x)而不是去脑补运算.

    我们再来开一下脑洞,既然sum返回的是一个函数,我们可以直接使用这些函数,没有必要再重复写一遍调用命令了,sumCube(1, 10) 类的语句可以省去不要了。

    
    def fact(n: Int): Int = 
      if (n == 0) 1 else n * fact(n - 1) 
     
    // 高阶函数
    def sum(f: Int => Int): (Int, Int) => Int = { 
      def sumF(a: Int, b: Int): Int = 
       if (a > b) 0 else f(a) + sumF(a + 1, b) 
      sumF 
    }
    
    // 直接调用高阶函数 ! 
    sum(x => x * x * x) (1, 10) //=> sumCube(1, 10) 
    sum(x => x) (1, 10)      //=> sumInt(1, 10) 
    sum(x => x * x) (1, 10)   //=> sumSquare(1, 10) 
    sum(fact) (1, 10)       //=> sumFact(1, 10)
    
    

    最后我们还可以使用高阶函数的语法糖来进一步优化这段代码: 

    
    // 没使用语法糖的 sum 函数
     def sum(f: Int => Int): (Int, Int): Int = { 
     def sumF(a: Int, b: Int): Int = 
      if (a > b) 0 else f(a) + sumF(a + 1, b) 
     
     sumF 
    } 
    // 使用语法糖后的 sum 函数
     def sum(f: Int => Int)(a: Int, b: Int): Int = 
     if (a > b) 0 else f(a) + sum(f)(a + 1, b)
    

    我反而觉得用语法糖更容易理解一点,更倾向于我们学的数学语言。

    读者可能会问:我们把原来的sum函数转化成这样的形式,好处在哪里?答案是我们获得了更多的可能性,比如刚开始求和的上下限还没确定,我们可以在程序中把一个函数传给sum, sum(fact)完全是一个合法的表达式,待后续上下限确定下来时,再把另外两个参数传进来。对于 sum 函数,我们还可以更进一步,把 a,b 参数再转化一下,这样 sum 函数就变成了这样一个函数:它每次只能接收一个参数,然后返回另一个接收一个参数的函数,调用后,又返回一个只接收一个参数的函数。这就是传说中的柯里化,多么完美的形式!在现实世界中,的确有这样一门函数式编程语言,那就是 Haskell,在 Haskell 中,所有的函数都是柯里化的,即所有的函数只接收一个参数!

    
    // 柯里化后的 sum 函数
     def sum(f: Int => Int)(a: Int) (b: Int): Int = 
    if (a > b) 0 else f(a) + sum(f)(a + 1)(b) 
     
    // 使用柯里化后的高阶函数 ! 
     sum(x => x * x * x)(1)(10) //=> sumCube(1, 10) 
     sum(x => x)(1)(10)      //=> sumInt(1, 10)
     
    
    

    如有疑问请留言或者到本站社区交流讨论,感谢阅读希望能帮助到大家,谢谢大家对本站的支持!