lerefe
V2EX  ›  Java

请教关于函数式编程的优势

  •  
  •   lerefe · Oct 1, 2022 · 10314 views
    This topic created in 1498 days ago, the information mentioned may be changed or developed.

    最近在看函数式编程相关的内容,并且结合公司一些人用函数式变成的处理,产生一个疑问,从算法复杂度和速度来说,很多时候一个循环能做到的事情,会用 lambda 循环多次处理,这样做的根据是什么? MJVis.png

    65 replies  •  2022-12-03 11:27:48 +08:00
    golangLover
    golangLover
    Oct 1, 2022
    1
    其实不要盲目使用 parallelStream, 很多数据量不大的情况下适得其反。
    另外就是函数式的目的是为了写法,而不是为了速度
    golangLover
    golangLover
    Oct 1, 2022
    2
    现在的人这么卷吗?国庆还写代码
    lerefe
    lerefe
    OP
    Oct 1, 2022 via iPhone
    3
    @golangLover 哈哈哈,没买到票回不去,看点东西
    TWorldIsNButThis
    TWorldIsNButThis
    Oct 1, 2022 via iPhone❤️18
    4
    好处是第二种我看一遍代码的函数调用就知道在干嘛最后产生了什么结果

    第一种不去跟着他的代码在脑子里模拟运行一下的话不知道最后得到了个什么东西,看了跟没看一样
    lerefe
    lerefe
    OP
    Oct 1, 2022 via iPhone
    5
    @TWorldIsNButThis 这个角度很有说服力
    Nasei
    Nasei
    Oct 1, 2022 via iPhone
    6
    性能方面,如果你的数据量十分巨大,后面那种可以很方便的改成集群式并发处理
    TWorldIsNButThis
    TWorldIsNButThis
    Oct 1, 2022 via iPhone❤️1
    7
    grouping 的第二个参数可以直接写对 group 内元素的处理,collectors.maxby(comparator.comparing(Book::getPrice)),
    然后.values().stream().flatMap(Optional::stream)
    代码可以更简短一些
    rabbbit
    rabbbit
    Oct 1, 2022
    8
    个人理解就是更易读、易维护?缺点是某些场景下存在性能问题。
    挺适合前端的(数据量小,需求、接口、数据格式总是变来变去)
    lerefe
    lerefe
    OP
    Oct 1, 2022 via iPhone
    9
    @TWorldIsNButThis 感谢指教
    lerefe
    lerefe
    OP
    Oct 1, 2022 via iPhone
    10
    @Nasei 你说的是 parallelStream 吗
    L4Linux
    L4Linux
    Oct 1, 2022
    11
    而且第二种可重用性高一些,注释里也写了改一行就可以 XXX 。
    Nasei
    Nasei
    Oct 1, 2022 via iPhone
    12
    @lerefe 我说的是分布式计算的一种模式,你可以了解下 data parallel model 和 mapreduce
    agagega
    agagega
    Oct 1, 2022 via iPhone
    13
    不可变能提高抽象层次(有助于并行或者向量化等优化),同时增强可读性
    v2eb
    v2eb
    Oct 1, 2022 via Android❤️1
    14
    yayiji
    yayiji
    Oct 1, 2022 via Android❤️6
    15
    后者可读性强
    结构固若金汤

    可以参考马丁对编程范式的论述
    结构化编程 我们限制了 goto 的使用
    面向对象编程 我们限制了指针的能力
    函数式编程 我们限制了可变变量的使用

    The unavoidable price of reliability is simplicity.
    lerefe
    lerefe
    OP
    Oct 1, 2022
    16
    @v2eb 你也在看程序袁老爸的视频吗
    zhouyg
    zhouyg
    Oct 1, 2022
    17
    函数式是声明式编程的范畴,而上面的代码是典型的指令式编程。所以这里的优势就是声明式编程相对于指令式编程的优势,也就是 readability, usability
    QKgf555H87Fp0cth
    QKgf555H87Fp0cth
    Oct 1, 2022
    18
    停止使用 var
    zxCoder
    zxCoder
    Oct 1, 2022
    19
    @likunyan 请问为何
    Huelse
    Huelse
    Oct 1, 2022
    20
    很明显的区别之一就是第一种使用了变量,而第二种没有,或者说系统帮你维护了变量,
    这样的写法用老外的话来说就是代码很健壮
    nightwitch
    nightwitch
    Oct 1, 2022
    21
    无状态的函数在并行编程时可以避免引入锁
    july1995
    july1995
    Oct 1, 2022 via iPhone
    22
    @zxCoder var 因为作用域的问题,现在很少使用,一般用 let const 。var 还有重复声明的问题。
    yigecaiji
    yigecaiji
    Oct 1, 2022 via Android
    23
    @july1995 这是 Java ,不是 JavaScript ,还是说 Java 现在引入 let 了?
    wdwwtzy
    wdwwtzy
    Oct 1, 2022❤️4
    24
    不得不说,这 java 的 stream 太难用了,也太难看了。
    学学 C#的 linq 吧,大概是这样
    books.GroupBy(b=> b.CategoryId)
    .SelectMany(g => g.OrderBy(b=> b.Price).Take(1))
    .ToList();
    cp19890714
    cp19890714
    Oct 1, 2022 via Android
    25
    第二种,代码直接表明了意图,想做什么事就调用对应的函数,这更接近人的思维。
    第一种,往往要把代码看完,才能猜出意图,而且使用很多无实际意义的临时变量,增加心智负担。
    july1995
    july1995
    Oct 1, 2022 via iPhone
    26
    @yigecaiji 哈哈,抱歉,看错了看错了,图片加载不出来,又看到有人提到 var ,下意识认为这是 JavaScript 。
    paopjian
    paopjian
    Oct 1, 2022
    27
    看第一个还能理解,第二个感觉对函数的功能理解需要非常全面,忘了一个函数就得找半天相关方法了,感觉调用超过三层不用 ide 自动提示就不知道怎么写了
    FrankFang128
    FrankFang128
    Oct 1, 2022
    28
    函数式一直就不以执行速度为优先
    ktqFDx9m2Bvfq3y4
    ktqFDx9m2Bvfq3y4
    Oct 1, 2022 via iPhone
    29
    @wdwwtzy
    你就静静的看他们捧 Java 多好。
    Rache1
    Rache1
    Oct 1, 2022
    30
    @wdwwtzy Laravel 的 Collections 也不错 😏
    graycreate
    graycreate
    Oct 1, 2022 via iPhone
    31
    新的思路提高抽象层次,你想干啥告诉我(声明式)就行了,具体怎么实现的调用者不需要关心
    chihiro2014
    chihiro2014
    Oct 1, 2022
    32
    普通的写法只是让我们编写逻辑不混乱。函数式是抽象了过程,只关心入参和结果
    xuanbg
    xuanbg
    Oct 1, 2022
    33
    并不会有什么优势!
    计算机可不会管你是不是函数式,编译完都是机器码。CPU 可不会因为你的机器码由函数式的代码编译而来就执行得快一些。
    wupher
    wupher
    Oct 1, 2022
    34
    浅见:

    1. 实际执行时多次 map / collect / filter 会被合并至一起,提升效率
    2. 没有中间变量并发时更可靠和安全
    3. 于 Actor/flow/Reactor 模式下,每一步都可拆解为一个事件 /信号,可以更好的利用多核进行并发处理。
    mxT52CRuqR6o5
    mxT52CRuqR6o5
    Oct 1, 2022
    35
    有一些约定俗称的函数能让你快速明白代码干了些什么
    比较限制副作用类型代码的书写,减少阅读时的心智负担
    mxT52CRuqR6o5
    mxT52CRuqR6o5
    Oct 1, 2022❤️1
    36
    像你举的那个函数式的例子,逻辑写成了一条一条的,阅读的时候只需要从上往下阅读,只需要依次关注每一条的功能和作用,记住上一条代码的返回值就不需要再关心上一条代码具体做了些什么
    像非函数式的那个例子,阅读的时候就需要整体都做了些什么,他在第一行声明了一个 map ,接下来的阅读中就需要时刻关心 map 具体发生了些什么,当前 map 的状态是什么
    ChefIsAwesome
    ChefIsAwesome
    Oct 1, 2022
    37
    链,或者叫 flow ,因为函数是 pure 的,随便抽一段连续的,就得到了一个新的函数,就能拿到其它地方复用。这不就是程序员梦寐以求的最简单、也是最高级的模块化。
    lmshl
    lmshl
    Oct 1, 2022❤️1
    38
    资深函数式码农(自封)来扯两句:
    于我而言函数式最大的优势在于,容易写对,且容易分析,容易理解。同时附带了容易并行的优势
    而执行速度,函数式写法确实慢于专家优化过的指令式,但比普通 CRUD 农写的指令式代码更快是基本操作了。
    loveyu
    loveyu
    Oct 1, 2022 via Android
    39
    说句不好听的,见过大量在复杂业务逻辑盲目使用方式二导致的性能和阅读困难
    Anarchy
    Anarchy
    Oct 1, 2022
    40
    对我而言就两点好处:
    1. 提供操作符减少了部分操作代码编写
    2. 整个代码结构从上到下就是对应逻辑链条,熟悉操作符很快就能看懂逻辑了
    支撑使用的原因就是第二条了
    Mogeko
    Mogeko
    Oct 1, 2022 via iPhone
    41
    因为 Java 的函数式本来就是残废的。

    像 Haskell 这类正统的函数式语言,默认珂里化,甚至参数都不用写全。灵活又高效。

    另外函数式编程最大的爽点是无副作用所带来的心智负担的降低;以及超高的鲁棒性。在这两点好处面前,性能的些微下降不值一提。
    iseki
    iseki
    Oct 1, 2022
    42
    第一种我必须人脑运行一次才能理解发生了什么;第二种虽然能一眼看明白在干啥,但是写的有点恶心,但我认为这是 Java 的问题,用 Kotlin 就好了
    zmal
    zmal
    Oct 1, 2022
    43
    函数式写法很大程度上是为了增加可读性,但 op 你发的这个写法 2 个人认为挺拉的。
    可读性没降低多少,时间复杂度从 n 升级到 n * log n 。
    lmshl
    lmshl
    Oct 1, 2022
    44
    写法 2 性能差不是 fp 的原因,而是楼主没能等价改写。
    实际上这里应该用 foldLeft 而不是 sorted/findFirst
    在 java stream api 中应该 reduce 是可以用的
    这样两段代码复杂度就一样了
    zmal
    zmal
    Oct 1, 2022
    45
    如果是我的话可能会这样写:

    https://imgur.com/YBBKeIT
    abc612008
    abc612008
    Oct 1, 2022❤️2
    47
    来个 kotlin 版本,比 java stream 舒服很多。(吹爆 kotlin

    ```kotlin
    data class Book(val category: String, val price: Double)

    fun mostExpensiveByCategory(books: List<Book>): Map<String, Book> {
    return books.groupBy { it.category }
    .mapValues { (_, books) -> books.maxBy { it.price } }
    }
    ```
    msg7086
    msg7086
    Oct 2, 2022
    48
    函数式一般可以把算法轻松地拆分成多个步骤。
    第一种写法的坏处就是在任何一个时间点的数据都缺乏一致性,即一半数据处理了,一半数据没处理。
    如果数据出错,你打断点拿到的也是这样一半一半的数据。
    函数式这样每次跑一步出来都是完整的数据集,一眼就能找到问题点。
    zjp
    zjp
    Oct 2, 2022
    49
    一次遍历就可以了,感觉楼上代码都被楼主带偏
    每个 key 对应单值而不是集合的优先考虑 Collectors.toMap()

    dataSource.stream().collect(Collectors.toMap(Book::getCategory, Function.identity(),
    (o1, o2) -> o1.getPrice().compareTo(o2.getPrice()) > 0 ? o1 : o2)).values();

    理论上总能用 lambda 写出一样的复杂度,只是可能有 API 的限制
    GeruzoniAnsasu
    GeruzoniAnsasu
    Oct 2, 2022
    50
    > 从算法复杂度和速度来说

    从这些角度来说函数式不一定有优势。




    函数式编程的本质是定义 A 到 B 的变换映射,当映射比流程更明确时,函数式更容易写对;反之若步骤和流程比映射更明确,那么强行使用函数式风格则是下策。
    zhuweiyou
    zhuweiyou
    Oct 2, 2022
    51
    这点数据 循环一次和循环几次没啥差别, 我的原则是能一行代码解决的 不写七八行.
    zddwj
    zddwj
    Oct 2, 2022 via Android
    52
    链式调用并不是函数式编程的专利,函数式编程的核心特征是不对变量进行重复赋值
    Envov
    Envov
    Oct 3, 2022
    53
    函数式编程很多时候都会牺牲一定的性能,但是获得了可读性上的增强。
    我个人很喜欢在 javascript 里面使用函数式,比如说组合函数,纯函数,hof 等等,但是同事都不是很认可,后来放弃了。只在自己的项目里面用。
    AllenHua
    AllenHua
    Oct 3, 2022 via iPhone
    54
    Java 可真残废 (doge )
    optional
    optional
    Oct 3, 2022
    55
    可读性强,可测试性高,可并行化改造,可以从组合的维度思考问题。同时流程清晰,可以很方便的进行优化,包括语法编译层面的优化。
    QKgf555H87Fp0cth
    QKgf555H87Fp0cth
    Oct 3, 2022
    56
    @lerefe @july1995 不好意思,看错了 :->
    git00ll
    git00ll
    Oct 3, 2022
    57
    简单场景下,虽然第二种方式可读性更高一些。
    但是因为是简单场景,完全可以给这个方法加一行注释 “按照 xxx 分组,获取每个分组内 xxx 最小的元素”
    来解决可读性差的问题。

    但是第二种写法的扩展性就不如第一种了,如取分组内最小的和第二小的,改起来逻辑就没这么顺了。
    WispZhan
    WispZhan
    Oct 3, 2022 via Android
    58
    @lmshl zio 好用吗?
    acapla
    acapla
    Oct 4, 2022
    59
    楼主在看哪本书啊? 可以推荐一下学习资料吗?
    lmshl
    lmshl
    Oct 4, 2022❤️1
    60
    @WispZhan
    akka-stream 、zstream 、fs2 都有上生产环境,目前用下来总体感觉 zio 的模型是上手最快,最容易写的。

    akka-stream 的错误处理建模会很恶心,fs2 和 zstream 差不多但是 zstream 的类型没有 fs2 那么高理解成本。
    Mistwave
    Mistwave
    Oct 4, 2022 via iPhone
    61
    @yayiji 请问这个原文哪里有?
    yayiji
    yayiji
    Oct 4, 2022 via Android❤️1
    62
    @Mistwave
    来自 架构整洁之道

    「如你所见,我在介绍三个编程范式的时候,有意采用了上面这种格式,目的是凸显每个编程范式的实际含义——它们都从某一方面限制和规范了程序员的能力。没有一个范式是增加新能力的。也就是说,每个编程范式的目的都是设置限制。这些范式主要是为了告诉我们不能做什么,而不是可以做什么。

    另外,我们应该认识到,这三个编程范式分别限制了 goto 语句、函数指针和赋值语句的使用。那么除此之外,还有什么可以去除的吗?

    没有了。因此这三个编程范式可能是仅有的三个了——如果单论去除能力的编程范式的话。支撑这一结论的另外一个证据是,三个编程范式都是在 1958 年到 1968 年这 10 年间被提出来的,后续再也没有新的编程范式出现过。」
    xavierchow
    xavierchow
    Oct 5, 2022
    63
    仅从题主的例子来说,
    第 1 点不同是可读性,imperative VS declarative ,
    第 2 点关于复杂度的担忧(循环),同样是函数式的写法可以用 fold / reduce, 另外尽量用 transducer ,看上去很多的 transformer 实际上也只是循环一次。(不清楚 java 是否有类似的东西,https://clojure.org/reference/transducers 是 clojure 在性能方面的考量和努力)
    amlee
    amlee
    Oct 6, 2022
    64
    @TWorldIsNButThis 换一种说法,好像就是把代码从命令式变成声明式
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   1738 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 161ms · UTC 13:07 · PVG 21:07 · LAX 05:07 · JFK 08:07
    ♥ Do have faith in what you're doing.