谈谈代码:函数式编程

简介: 一个风和日丽的下午,我看着日常看代码做重构迁移,突然看到这么段代码...

theme: awesome-green

版本 日期 备注
1.0 2019.8.11 文章首发
1.1 2021.5.21 修改标题:浅谈函数式编程-> 谈谈代码:函数式编程

1. 前言

一个风和日丽的下午,我看着日常看代码做重构迁移,突然看到这么段代码:

    private void getTopicsDiskSizeForSomeBroker(int brokerID, AdminClient admin, Map<String, Long> topicsSizeMap) throws ExecutionException, InterruptedException {
        DescribeLogDirsResult ret = admin.describeLogDirs(Collections.singletonList(brokerID));
        Map<Integer, Map<String, DescribeLogDirsResponse.LogDirInfo>> tmp = ret.all().get();
        for (Map.Entry<Integer, Map<String, DescribeLogDirsResponse.LogDirInfo>> entry : tmp.entrySet()) {
            Map<String, DescribeLogDirsResponse.LogDirInfo> tmp1 = entry.getValue();
            for (Map.Entry<String, DescribeLogDirsResponse.LogDirInfo> entry1 : tmp1.entrySet()) {
                DescribeLogDirsResponse.LogDirInfo info = entry1.getValue();
                Map<TopicPartition, DescribeLogDirsResponse.ReplicaInfo> replicaInfoMap = info.replicaInfos;
                for (Map.Entry<TopicPartition, DescribeLogDirsResponse.ReplicaInfo> replicas : replicaInfoMap.entrySet()) {
                    String topic = replicas.getKey().topic();
                    Long topicSize = topicsSizeMap.get(topic);
                    if (topicSize == null) {
                        topicsSizeMap.put(topic, replicas.getValue().size);
                    } else {
                        topicsSizeMap.put(topic, replicas.getValue().size + topicSize);
                    }
                }
            }
        }
    }

看了这段代码我整个人都不好了!

首先是那火箭式的三个嵌套for循环,再者就是那些变量声明语句.为了迭代他们,我们不得不声明它一遍...

2. 使用Stream

    public List<KafkaTopicInfoBO> getTopicDiskSize() {
   
   
        return getTopicPartitionReplicaInfo().entrySet().stream()
                .map(e -> new KafkaTopicInfoBO(e.getKey().topic(), e.getValue().size))
                .collect(Collectors.toList());

    }

    protected Map<TopicPartition, DescribeLogDirsResponse.ReplicaInfo> getTopicPartitionReplicaInfo() {
   
   
        Properties globalConf = zkConfigService.getProperties(ZkPathUtils.GLOBAL_CONFIG);
        Properties adminConfig = new Properties();
        adminConfig.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, globalConf.getProperty((ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG)));
        AdminClient adminClient = AdminClient.create(adminConfig);
        List<String> brokerIds = zkConfigService.getChildByPath(kafkaIdsPath);
        return  brokerIds.stream()
                .map(Integer::valueOf)
                .map(Collections::singletonList)
                .map(adminClient::describeLogDirs)
                .map(DescribeLogDirsResult::all)
                .map(mapKafkaFuture -> {
   
   
                    try {
   
   
                        return mapKafkaFuture.get();
                    } catch (Exception e) {
   
   
                        throw new RuntimeException(e);
                    }
                })
                .map(Map::values)
                .flatMap(Collection::stream)
                .map(Map::values)
                .flatMap(Collection::stream)
                .map(e -> e.replicaInfos)
                .map(Map::entrySet)
                .flatMap(Collection::stream)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

这样看起来似乎好了一点.但是对于不熟悉函数式编程的同学来说,理解以上代码还是有点困难的.

接下来,先来简单讲一讲函数式编程.

3. 什么是函数式编程

3.1 一句话搞懂

就像来自数学中的代数

f(x)=5x^2+4x+3
g(x)=2f(x)+5=10x^2+8x+11
h(x)=f(x)+g(x)=15x^2+12x+14

函数式编程定义输入数据和输出数据相关的关系——数学表达式里面其实是在做一种映射(Mapping),输入的数据和输出的数据关系是什么样的,就是用来函数定义的.

3.2 直观感受:用代码举例

public class Quint{
   
   
    public static void main (String args[]){
   
   
        for (int i=0; i<25; i++){
   
   
            System.out.println(i*i);
        }
    }
}
(println (take 25 (map (fn [x] (*x x) (range)))))

简单解释一下上段Lisp代码:

  1. range函数回返回一个从0开始的整数无穷列表
  2. 然后该列表会被传入map,针对列表中的每个元素,调用平方值的匿名函数,产生了一个无穷多的,包含平方值的列表
  3. 将列表传入take函数,仅仅返回前25个
  4. println将接入的参数输出

4. 使用对函数式编程支持更好的Kotlin


    protected fun getTopicPartitionReplicaInfo(): Map<TopicPartition, DescribeLogDirsResponse.ReplicaInfo> {
        val globalConf = zkConfigService.getProperties(ZkPathUtils.GLOBAL_CONFIG)
        val adminConfig = Properties()
        adminConfig.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, globalConf.getProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG))
        val adminClient = AdminClient.create(adminConfig)
        val brokerIds = zkConfigService.getChildByPath(kafkaIdsPath)
        return brokerIds.stream()
                .mapToInt {
                    Integer.valueOf(it)
                }.let { intStream ->
                    adminClient.describeLogDirs(intStream.boxed().collect(Collectors.toList()))
                }.let { describeLogDirsResult ->
                    describeLogDirsResult.all()
                }.let { mapKafkaFutrue ->
                    mapKafkaFutrue.get()
                }.let { mapStream ->
                    mapStream.values
                }.let {
                    it.stream().map { e -> e.values }.flatMap { e -> e.stream() }.collect(Collectors.toList())
                }.flatMap {
                    it.replicaInfos.entries.toList()
                }.let { it ->
                    it.associateBy({ it.key }, { it.value })
                }
    }

代码看起来大差不差.但Kotlin的这些关键字写起来更方便.我们看下Java中map函数和Kotlin中let函数的签名:

     * Returns a stream consisting of the results of applying the given
     * function to the elements of this stream.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param <R> The element type of the new stream
     * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *               <a href="package-summary.html#Statelessness">stateless</a>
     *               function to apply to each element
     * @return the new stream
     */
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#let).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

我们可以看到Java中的map是被限制在Stream API中的,而Kotlin的let并没有这种限制.

同时,我们也可以感受到,对于函数式编程的支持,明显是Kotlin更好一些——在Kotlin中,我们用一个{}就可以表示函数,而Java则需要Interface来表示(在Java中,对象是一等公民).

如果读者有兴趣的话,可以尝试一下Haskell或Lisp(JVM上叫Clojure).这些都是纯函数式语言.

类似,Kotlin还有很多这种函数,被称为作用域函数,在这里罗列一下常用的函数:

  • let
  • run
  • also
  • apply
  • takeIf
  • takeUnless
  • repeat

5. 小结

在《架构整洁之道》中,有这么一个总结:

  • 结构化编程是对程序控制权的直接转移的限制
  • 面向对象编程是对程序控制权的间接转移的限制
  • 函数式编程是对程序赋值操作的限制

如果说面向对象编程是对数据进行抽象,那么函数式编程则是对行为进行抽象.

5.2 函数式编程的三件套:

  1. Map
  2. Reduce
  3. Filter

举个例子,面包和蔬菜map到切碎的操作上,再reduce成汉堡.

我们可以看到map和reduce不关心输入数据,它们只控制,并不是业务.控制是描述怎么干,而业务描述要干什么.

在本文中,我们只看到了map的身影——上面提到了,map对流中的每一个元素进行操作.

可能会有读者问let是啥,在本文的代码例子中,let针对整个流进行操作.

简单来说, Map && Reduce 对应了我们日常中用的循环,而Filter对应了If

5.3 优势 && 劣势

  • 优势

    1. 无状态
    2. 并发无伤害
    3. 函数执行没有顺序上的问题
  • 劣势

    1. 数据复制严重

5.4 应用场景

  • Python的装饰器模式
  • 事件溯源:不记录最终状态,而是记录每一个事件.需要时,通过追溯(重新计算)事件来得出当前的状态.如:
    1. 数据库事务日志
    2. 版本控制器
    3. 比特币
目录
相关文章
|
19天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
8831 25
|
18天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
3560 16
|
18天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
2215 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
12天前
|
人工智能 Linux 开发者
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
Codex是OpenAI推出的AI编程智能体,可读取本地项目、理解需求并自动修改代码。支持桌面GUI、命令行(CLI)及VS Code/Cursor插件三种形态,覆盖可视化操作、终端高效开发与编辑器无缝集成场景,助开发者用自然语言驱动编码全流程。(239字)
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
|
18天前
|
云安全 人工智能 安全
|
4天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
388 1
|
7天前
|
人工智能 Linux Windows
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面
千问办公(QwenWork)是阿里云推出的AI智能办公平台,支持网页端直接使用及Windows/Mac/Linux客户端下载。提供PPT生成、财报分析、网页搭建等AI功能,个人版免费,企业版198元/席/月。详情见官网qwenwork.cn或阿里云产品页。
878 0
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面

热门文章

最新文章