zoukankan      html  css  js  c++  java
  • 函数式编程

    工作以来, 在编写程序的时候一直使用面向对象的思想. 当然, 对函数式编程也有所耳闻, 但也仅仅是有所耳闻, 从来没有上手写过.

    最近没事的时候就找些资料看看, 同时也尝试自己写一些函数式编程思想的代码. 毕竟这也是一种编程思想嘛, 虽然应用没有面向对象这么广泛(当然, 也可能仅仅是我觉的, 毕竟我在使用中全部都是面向对象), 但了解其编程思想, 对于解决问题也提供一种新的思路不是.

    以下简单总结一下我最近对函数式编程的体验.

    最开始, 我以为将面向对象中的类为基本单位, 换成函数为基本单位, 就是函数式编程了, 结果发现, 这只能说明我还是在使用面向对象的思想.

    那么什么是函数式编程呢?

    看到函数这个名字, 最先想到的就是初中的数学了: f(x)=2x. 这是一个一元一次函数.

    同时, 在对各种函数进行计算的时候, 还会用到函数的嵌套, 比如:

    • f(x)=2x
    • g(x)=x+2
    • q(x)=g( f(x) )

    这种函数的嵌套关系, 是不是也能应用到编程中呢? 没错. 比如这样一个需求: 输出列表中奇数的个位数.

    传统的写法如下(PHP 版本):

    function dispose($arr){
        foreach ($arr as $item){
            // 过滤偶数
            if($item % 2 == 0) continue;
            // 取出个位数字
            $digit = $item % 10;
            // 将个位数字输出
            echo '当前数字: '.$digit, PHP_EOL;
        }
    }
    
    dispose([12, 24, 37, 115]);
    

    如果写成这种嵌套形的呢?

    // 过滤偶数
    function filterEvent($arr){
        foreach ($arr as $item){
            if($item % 2 == 0) continue;
            yield $item;
        }
    }
    
    // 取出个位数
    function getDigit($arr){
        foreach ($arr as $item){
            yield $item % 10;
        }
    }
    
    // 将数字转成字符串
    function getEchoStr($arr){
        foreach ($arr as $item){
            yield '当前数字: '.$item.PHP_EOL;
        }
    }
    
    // 输出数组每一个选项
    function echoItem($arr){
        foreach ($arr as $item){
            echo $item;
        }
    }
    
    echoItem(
        getEchoStr(
            getDigit(
                filterEvent([12, 24, 37, 115])
            )
        )
    );
    

    后面这个是不是只看调用, 也能够清楚的看到其执行过程, 但是看起来有些丑. 类似于一个管道, 数据依次从管道中流过, 拿到最终的结果. 等等, 管道, 怎么感觉有点眼熟.

    image-20210415231803940

    linux 中的命令使用的不就是这种思想么. 函数嵌套确实比较丑陋, 同时每一个方法中都需要进行遍历, 重复代码过多. 但是如果能够像 linux 的命令这样, 那就好看了. 别说, 还真有, 不过是在 Python 中实现的(通过运算符重载), 看到下面这个实现, 你一定会觉得很漂亮, 因为我第一次写出来的时候眼前一亮.

    class Pipe(object):
        def __init__(self, func):
            self.func = func
    
        # 此方法当 位运算 | 左侧操作符不支持的时候调用
        def __ror__(self, other):
            for item in other:
                if item is None:
                    continue
                yield self.func(item)
    
    @Pipe
    def filter_event(item):
        return item if item % 2 != 0 else None
    
    @Pipe
    def get_digit(item):
        return item % 10
    
    @Pipe
    def get_echo_str(item):
        return '当前数字: ' + str(item)
    
    @Pipe
    def echo(item):
        print(item)
    
    def pipeline(sqs):
        # 这里因为前面都是迭代器, 所以需要一个空遍历, 否则函数不会执行
        for item in sqs: pass
    
    
    arr = [12, 24, 37, 115]
    pipeline(arr | filter_event | get_digit | get_echo_str | echo)
    

    看这个调用是不是和 Linux 的命令行一样?

    在函数式编程中, 对数据的处理有如下三种方式:

    1. map: 对数据进行转换, 一对一
    2. filter: 对数据进行过滤
    3. reduce: 对数据进行聚合

    一个数据源, 流过各个管道, 通过以上三种方式进行处理, 得到最终结果. 等等, 这不就是spark的处理思路嘛.

    在纯函数式编程中, 函数是不会保存外部状态的, 对于一个函数, 接收确定输入的同时, 会返回确定的输出. 故而也不用考虑并发的问题, 同时因为没有外部状态, 对于单元测试来说也极度友好.

    针对我对于函数式编程的使用来看, 总结函数式编程的几个特点, 可能并不全面:

    1. 管道操作. 可以将数据通过依次流过各个管道, 将各种简单的操作整合为一个复杂的操作.
    2. 将函数作为头等对象
    3. 延迟处理. 这个是我自己认为的. 既然函数对外部没有影响, 那么函数的返回值就可以在真正使用的时候在获得.
    4. 没有并发问题. 仅针对于纯函数编程.

    当然, 我也尝试着使用函数式编程实现一些稍微复杂一些的功能, 怎么说呢. 在完成一些较复杂功能的时候, 感觉函数式编程思想并没有那么好用, 很可能是因为我在很大程度上思想还没有转变过来, 所以写起来比较费力.

    不过, 就一些简单的例子来说, 个人感觉管道的操作确实十分优美.

    此外, 函数式编程不止以上内容,  这段时间只是简单的尝试 

  • 相关阅读:
    【oracle】查看表空间对应文件所在位置
    【oracle】查看表空间信息
    【java异常】java.lang.Integer cannot be cast to java.lang.String
    【oracle】DATE输出是什么东西
    数字万用表的精度和分辨率,ADC的位数
    二阶系统
    Verilog中实现电平检测
    模拟信号和数字信号,直流信号和交流信号
    噪声:强度,方差信噪比(待完善)
    Simulink模块之Band-Limited White Noise
  • 原文地址:https://www.cnblogs.com/hujingnb/p/14669131.html
Copyright © 2011-2022 走看看