【架构思路】函数式编程想法及小栗子

参考资料网页链接: https://juejin.cn/post/6878941871259779085 (函数式编程的实用场景)
https://github.com/xiaolingzang/python-skills/blob/master/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B.md (函数式编程)
https://coolshell.cn/articles/10822.html(函数式编程)

本文整理了一些网络上的资料和介绍,同时依据初步理解编写了一个简单的对比demo,形成本篇。如有问题欢迎评论指正。

1 函数式编程

函数式编程中的「函数」指的是数学中的函数,即自变量的映射。函数的值取决于函数的参数的值,不依赖于其他状态,比如abs(x)函数计算x的绝对值,只要x不变,无论何时调用、调用次数,最终的值都是一样,即纯函数(pure function):
纯函数的条件:

  1. 函数内部不会依赖和影响外部的任何变量 (如果函数内使用了全局变量,就不能称为纯函数)
  2. 相同的输入,永远会得到相同的输出

纯函数的优点:

1. 可缓存
2. 可测试( 非纯函数代码是很难测试的)
3. 易于并发

先看一个非函数式的例子:

int cnt;

void increment(){

cnt++;

}

那么,函数式的应该怎么写呢?

int increment(int cnt){

return cnt+1;

}

你可能会觉得这个例子太普通了。是的,这个例子就是函数式编程的准则:不依赖于外部的数据,而且也不改变外部数据的值,而是返回一个新的值给你

在函数式编程中,函数就是一个流水线,传递进去的数据(参数)就像是要加工的产品,我们可以通过用不同的加工设备加工出不一样的产品,但是这条流水线始终不会出现其他产线的任务产品

函数式编程是一种声明式编程。(面向对象编程是一种命令式编程)

  • 命令式编程是面向计算机硬件的抽象,一个命令式程序就是一个冯诺依曼机的指令序列
  • 函数式编程是面向数学的抽象,将计算描述为一种表达式求值。一个函数式程序就是一个表达式。

因此:函数式编程就是属于声明式编程范式,这种范式会描述一系列的操作,但并不会暴露它们是如何实现的或是数据流如何传过它们。

函数式编程的几个技术:

  • map & reduce函数式编程最常见的技术就是对一个集合做Map和Reduce操作。这比起传统的面向过程的写法来说,在代码上要更容易阅读(不需要使用一堆for、while循环来倒腾数据,而是使用更抽象的Map函数和Reduce函数)。
  • pipeline※
    这个技术的意思是把函数实例成一个一个的action,然后把一组action放到一个数组或是列表中组成一个action list,然后把数据传给这个action list,数据就像通过一个pipeline一样顺序地被各个函数所操作,最终得到我们想要的结果。
  • recursing
    递归最大的好处就简化代码,它可以把一个复杂的问题用很简单的代码描述出来。注意:递归的精髓是描述问题,而这正是函数式编程的精髓。
  • currying
    把一个函数的多个参数分解成多个函数, 然后把函数多层封装起来,每层函数都返回一个函数去接收下一个参数这样,可以简化函数的多个参数(减少函数的参数数目)。
  • higher order function※
    高阶函数:所谓高阶函数就是函数当参数,把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进传出。

函数式编程的几个好处

  • parallelization 并行
    在并行环境下,各个线程之间不需要同步或互斥(变量都是内部的,不需要共享)。
  • lazy evaluation 惰性求值
    表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值。
  • determinism 确定性
    输入是确定的,输出就是确定的。

可以看到函数式编程减少了变量的使用,也就减少了出Bug的可能,维护更加方便。可读性更高,代码更简洁。

我们可以很清楚地看到程序的主干,把代码逻辑封装成了函数后,我们就相当于给每个相对独立的程序逻辑取了个名字,于是代码成了自解释的
但是,你会发现,在非函数式编程中,将功能封装成函数后,这些函数都会依赖于共享的变量来同步其状态。于是,我们在读代码的过程时,每当我们进入到函数里,一量读到访问了一个外部的变量,我们马上要去查看这个变量的上下文,然后还要在大脑里推演这个变量的状态, 我们才知道程序的真正逻辑。也就是说,这些函数间必需知道其它函数是怎么修改它们之间的共享变量的,所以,这些函数是有状态的
我们知道,有状态并不是一件很好的事情,无论是对代码重用,还是对代码的并行来说,都是有副作用的。因此,我们要想个方法把这些状态搞掉,于是出现了我们的 Functional Programming 的编程范式。

下面附上一个小栗子 小例子来对比两种编程方法(函数式与非函数式),该代码是基于我的浅薄理解,有问题还请随时指出。

2 例子

2.1 非函数式例子

//
// Created by greg on 7/18/22.
//


/**
 * 对data中的数据进行反序、*2、并按顺序输出3的倍数
 */
#include <iostream>
#include <stack>
int iArraySize = 10;
double data_arr[10] = {1,2,3,4,5,6,7,8,9,10};
double multiply = 2.0;

void process(double * _arr){
    std::stack<double> stDouble;
    for(int i  = 0; i < iArraySize; i++)
    {
        stDouble.push(_arr[i]);
    }
    for (int i = 0; i < iArraySize;i++)
    {
        _arr[i] = stDouble.top()*multiply;
        stDouble.pop();
    }
    for (int i = 0 ; i < iArraySize; i++)
    {
        if ((int)_arr[i]%3 == 0)
        {
            std::cout<<" "<<_arr[i];
        }

    }

}

int main() {

    process(data_arr);
    return 0;
}

process中处理了整个的操作过程,同时操作过程依赖了外部变量iArraySize 与 multiply。如果去掉注释的话,这简单的代码阅读起来还是要稍加理解才能明白操作过程。程序的输出如下:
在这里插入图片描述
如果此时,外部变量iArraySize 被“不小心”的改动为如12,则程序的输出如下:
在这里插入图片描述
此时,debug就要花费一些时间了,诸如整理逻辑、添加log等。

2.2 函数式例子

//
// Created by greg on 7/18/22.
//

/**
 * 对data中的数据进行反序、*2、并输出3的倍数
 */
#include <iostream>
#include <stack>
#include <vector>


std::vector<double> data_arr = {1,2,3,4,5,6,7,8,9,10};

std::vector<double>  printArr(std::vector<double>  _arr){
//    std::cout<<"in func_printArr"<<std::endl;
    for (int i = 0 ; i < _arr.size(); i ++)
    {
        std::cout<<" "<<_arr[i];
    }
    std::cout<<std::endl;
//    std::cout<<"end of func_printArr"<<std::endl;
    return _arr;
};

std::vector<double>  func_reverse(std::vector<double>  _arr)
{
//    std::cout<<"in func_reserve"<<std::endl;
    std::stack<double> stDouble;
    std::vector<double> stResult;
    for (int i = 0 ; i < _arr.size(); i ++)
    {
        stDouble.push(_arr[i]);
    }
    for (int i = 0 ; i < _arr.size(); i ++)
    {
        stResult.push_back(stDouble.top())  ;
        stDouble.pop();
    }
//    std::cout<<"end of func_reserve"<<std::endl;
//    printArr(_arr);
    return stResult;
}

std::vector<double>  multiply2(std::vector<double>  _arr){

//    std::cout<<"in func_multiply2"<<std::endl;
    for (int i = 0 ; i < _arr.size(); i ++)
    {
        _arr[i] = _arr[i]*2;
    }
//    std::cout<<"end of func_multiply2"<<std::endl;
//    printArr(_arr);
    return _arr;
}

std::vector<double>  get3factor(std::vector<double>  _arr)
{
//    std::cout<<"in func_get3factor"<<std::endl;
    std::vector<double> temp,re;
    for (int i = 0 ; i < _arr.size(); i ++)
    {
        if ((int) _arr[i] % 3 == 0)
        {
            temp.push_back(_arr[i]);
        }
    }
    for (int k = 0 ; k < temp.size() ; k ++)
    {
        re.push_back(temp[k]);
    }

//    printArr( re);
    return re;



}



void process(std::vector<double>  (*pGetFunctionError_Class[])(std::vector<double> ),std::vector<double> & _arr ,int _func_n){
    std::vector<double>  _p = _arr ;
    for (int n = 0 ; n < _func_n; n++)
    {
        _p = pGetFunctionError_Class[n](_p);
    }
}

int main() {
    std::vector<double>  (*funcs[4])(std::vector<double> ) = {func_reverse,multiply2,get3factor,printArr};
    std::cout<<"the result of process is :"<<std::endl;
    process(funcs,data_arr,4);




    //test

    std::cout<<"-------------------Test Part: ----------------------"<<std::endl;
    std::cout<<"this is raw array:"<<std::endl;
    printArr(data_arr);
    std::cout<<"this is test of reserve:"<<std::endl;
    printArr(func_reverse(data_arr));
    std::cout<<"this is test of multiply2:"<<std::endl;
    printArr(multiply2(data_arr));
    std::cout<<"this is test of get3factor:"<<std::endl;
    printArr(get3factor(data_arr));
    std::cout<<"this is test of printArr:"<<std::endl;
    printArr(data_arr);

    return 0;
}



可以看到,整个过程分为了四步,在main中比较清晰的展示了:
std::vector<double> (*funcs[4])(std::vector<double> ) = {func_reverse,multiply2,get3factor,printArr};

  • func_reverse: 反序
  • multiply2: 乘法2
  • get3factor: 获取3的倍数
  • printArr: 打印字符串

整体的输出如下:(隐去Test输出部分)
在这里插入图片描述
如果此时由于某种“不小心”将multiply2中的*2变为了 *4,加入Test输出后如下:
在这里插入图片描述
可以看到,当出现问题后,可以很方便的分别调试不同函数部分的输出:反序、获取3倍数、打印数组都没有问题,看到multiply2的数组输出变为了4倍。因此快速定位了问题所在。这也印证了上文所述的函数式编程的优点:可测试结果确定

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值