初探函数式编程的优点

初探函数式编程的优点

函数式编程是一种编程的模式,在这种编程模式中最常用的函数和表达式。它强调在编程的时候用函数的方式思考问题,函数也与其他数据类型一样,处于平等地位。可以将函数作为参数传入另一个函数,也可以作为别的函数的返回值。函数式编程倾向于用一系列嵌套的函数来描述运算过程。

函数式编程的优点

不可变数据

原则上来讲,在函数式编程中应当做到函数中变量不修改,函数不依赖外部数据,所有的变量需要修改时都应当将原变量作为参数传入到函数中,并在函数中通过运算返回一个新的变量。
例如下面代码:


int sum;
int increment(int a){
    sum = sum + a;
    return sum;
}

如果按照函数式编程方法则应当写成:


def increment(sum: Int, a: Int) = {
    sum + a
}

代码本身没有什么问题,但是使用下面的表达方式会解决第一种方式的三个问题。第一,上面的increment方法中依赖了成员变量sum,因此每次调用该方法返回的值根据成员变量的状态会发生变化,这在代码出现问题debug的时候会很难排查问题,在编写单元测试的时候也需要注意成员变量的状态。第二,第一种写法的increment方法其实做了两件事,即修改成员变量的值和增加后返回成员变量。调用者在调用这个方法的时候很可能只为了获取增加后的数值,并没有意识到会改变成员变量,也会后面的代码埋下了隐患。第三,在并发的情况下第一种方法的结果更难以预测,而使用函数式编程由于没有改变任何变量,因此不存在并发问题。

强制使用递归:

在函数式编程中,函数是没有状态的,不存在可变变量的,那么如果真的在程序中需要用到状态的时候应当怎么办呢?举例来说如果要求x的n次方,我们可能会写如下代码。


long power(int x, int p){
    long result = 1;
    for(int i = 0; i < p; i++)
        result = result * x;
    return result;
}

其中用result记录了每次计算后得到的值。那么如果使用函数式编程应当如何实现这个函数呢?答案是将每次的状态都作为参数传递到函数中去,说白了就是递归。


def power(x: Int, p: Int): Long = {
    if(p == 0)
        return 1
    else
        return x * power(x, p - 1)
}

递归对于函数式编程来说是非常重要和常用的,在利用函数式编程解决问题的很多时候递归不是可选而是必选的方式。这也一定程度上使得代码更具有可读性,当然,可读性的问题仁者见仁智者见智,对每个人来说习惯的就是可读的,但是我认为递归和循环最本质的区别在于,循环是思考如何解决这个问题,而递归是思考如何描述这个问题。从这个角度来看,无论是编写还是阅读,递归都有着一定的优势。
很多同学可能会质疑,从性能上来说,递归有着难以避免的占用堆栈空间问题。不过函数式编程的尾递归优化可以一定程度上解决这个问题。在前面讨论递归的时候我们说过,递归可以将需要保存的状态变量通过参数方式传递给下一层的函数,也就是说如果递归发生在函数最后一行的话,那函数前面的所有内容其实都不需要存储,递归的函数也就可以重新利用当前的栈空间,这样以来递归函数所需要的占空间就是常数阶的了。

高阶函数和闭包:

函数式编程中的高阶函数和闭包能够减少代码的重复率,让代码更具有模块化。比如说需要实现三个字符串过滤器,分别完成根据字符串尾过滤,根据字符串包含过滤以及根据正则式过滤。我们可能会写下这样的代码。


public List strEnding(List list, String query){
        List result = new ArrayList();
        for (String str : list) {
            if(str.endsWith(query))
                result.add(str);
        }
        return result;
    }
    public List strContaining(List list, String query){
        List result = new ArrayList();
        for (String str : list) {
            if(str.contains(query))
                result.add(str);
        }
        return result;
    }
    public List strRegex(List list, String query){
        List result = new ArrayList();
        for (String str : list) {
            if(str.matches(query))
                result.add(str);
        }
        return result;
    }

如果使用高阶函数则可以将过滤函数作为参数传递到函数中,即


def strMatching(list: List[String], query: String, matcher:(String, String) => Boolean) = {
        for(str <- list; if(matcher(str, query))) yield str
    }
def strEnding(list: List[String], query: String) = strMatching(list, query, _.endsWith(_))
def strContaining(list: List[String], query: String) = strMatching(list, query, _.contains(_))
def strRegex(list: List[String], query: String) = strMatching(list, query, _.matches(_))

这样一来就提高了代码的重用率。
以上是仅仅是本人初学scala的一些对于目前已有的一些观点的个人理解(基本上都是拾人牙慧和自己瞎扯)。一定有许多错误和理解不准确的地方,希望能够得到各位的批评指正和指点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值