前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >函数式编程

函数式编程

作者头像
烟草的香味
发布2021-04-28 10:51:24
9700
发布2021-04-28 10:51:24
举报
文章被收录于专栏:烟草的香味烟草的香味

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

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

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

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

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

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

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

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

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

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

代码语言:javascript
复制
function dispose($arr){
    foreach ($arr as $item){
        // 过滤偶数
        if($item % 2 == 0) continue;
        // 取出个位数字
        $digit = $item % 10;
        // 将个位数字输出
        echo '当前数字: '.$digit, PHP_EOL;
    }
}

dispose([12, 24, 37, 115]);

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

代码语言:javascript
复制
// 过滤偶数
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 中实现的(通过运算符重载), 看到下面这个实现, 你一定会觉得很漂亮, 因为我第一次写出来的时候眼前一亮.

代码语言:javascript
复制
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. 没有并发问题. 仅针对于纯函数编程.

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

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-04-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 烟草的香味 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档