网资酷

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 109|回复: 3

新流式编程(一):流的定义,一切都从forEach开始

[复制链接]

1

主题

4

帖子

5

积分

新手上路

Rank: 1

积分
5
发表于 2022-12-10 17:23:17 | 显示全部楼层 |阅读模式
相关链接

【目录】新流式编程(序)
各语言流式API的现状

支持流式API的语言其实不多,比较典型的代表是Java的Stream与Kotlin的Sequence(其实是我对这俩最熟。示例代码如下
Java流的示例
Stream.of(1, 2, 3, 4, 5)
    .limit(4)
    .map(i -> i * 2)
    .filter(i -> i % 3 > 0)
    .map(Object::toString)
    .collect(Collectors.joining(","));以上代码流程为

  • 首先生成一个1~5的流 -> 1, 2, 3, 4, 5
  • 截取前4个 -> 1, 2, 3, 4
  • 每个元素映射为原来的2倍 -> 2, 4, 6, 8
  • 过滤掉能被3整除的数 -> 2, 4, 8
  • 每个元素转为String -> "2", "4", "8"
  • 合并所有元素 -> "2,4,8"
Kotlin的流式API与Java类似,只是部分名称稍有不同
sequenceOf(1, 2, 3, 4, 5)
    .take(4)
    .map { it * 2 }
    .filter { it % 3 > 0 }
    .map { it.toString() }
    .joinToString(",")事实上,Java对流的实现依赖的是Spliterator,是一种特殊的Iterator,可以提供并发的额外好处。相比之下,Kotlin的实现是直接基于Iterator,要简单优雅很多。 为方便演示,后续的示例我主要还是用Java或者一些伪代码展示。
不妨换个思路,从forEach入手

基本上大多数支持了闭包的语言,都会对其集合类型list或者array提供一个for循环,更高级一点的,还有一个大家通常称之为forEach的函数式接口。该接口接受一个consumer作为入参:对于集合中的每一个元素,都进行某种特定处理。即
a.forEach(x -> println(x))等价于
for x in a
    println(x)现在不妨假设我们有[1,2,3,4]这样一个列表,使用forEach挨个打印它们将会打印出4行,分别是1,2,3,4。如果我们想打印成2,3,4,5,或者说,每个元素先分别+1再打印,该如何操作呢?
答案很容易,只需要打印的时候转换一下就行
forEach(i -> println(i + 1));这就够了,以上就是咱这个新式流机制的基本原理。为了更严谨的说明,这里我们需要引入一个流的定义,或者说接口
public interface Seq<T> {
    void forEach(Consumer<T> consumer);
}Seq是我对Sequence的简写,意味着序列操作。这里值得注意的是,Java里的Iterable是天然实现了这个接口的。
回到之前的例子。如果我们有一个代表[1,2,3,4]的oldSeq,现在想要得到一个代表[2,3,4,5]的新的newSeq ,根据上述的转换方式,利用Java的匿名类机制,可以很容易实现
Seq<Integer> newSeq = new Seq<Integer>() {
    @Override
    public void forEach(Consumer<Integer> consumer) {
        oldSeq.forEach(i -> consumer.accept(i + 1));
    }
};以上代码的含义为,对于任何一个操作consumer,都是在原有的元素上+1后再操作,这个操作可以是打印,也可以是别的任何行为。进一步的,借用Java 8的lambda函数,我们可以将其更简洁的写为
Seq<Integer> newSeq = c -> oldSeq.forEach(i -> c.accept(i + 1));至此,聪明的你可能会发现,我们基于平平无奇的forEach接口,推导实现出了第一个具有里程碑意义的函数式接口,那就是伟大的map! 于是我们有了
public interface Seq<T> {
    void forEach(Consumer<T> consumer);

    default <E> Seq<E> map(Function<T, E> function) {
        return c -> forEach(t -> c.accept(function.apply(t)));
    }
}顺理成章,我们还可以依样画葫芦,写出filter的实现
public interface Seq<T> {
    void forEach(Consumer<T> consumer);

    default <E> Seq<E> map(Function<T, E> function) {
        return c -> forEach(t -> c.accept(function.apply(t)));
    }

    default Seq<T> filter(Predicate<T> predicate) {
        return c -> forEach(t -> {
            if (predicate.test(t)) {
                c.accept(t);
            }
        });
    }
}到这里,我搞出来的这个新的流式API的定义就算讲清楚了。它的后续的一切强大接口和有趣功能,都是基于这样一个简单的forEach 而衍生出来的。
public interface Seq<T> {
    void forEach(Consumer<T> consumer);
}这个API是一切的基础,是梦开始的地方。它将带领大家一步步渐入佳境,沿途把橄榄枝抛向几乎所有主流非主流语言,并贯穿整个专栏始终。
回复

使用道具 举报

2

主题

10

帖子

18

积分

新手上路

Rank: 1

积分
18
发表于 2022-12-10 17:23:51 | 显示全部楼层
像 Clojure 的 Transducers
回复

使用道具 举报

1

主题

2

帖子

3

积分

新手上路

Rank: 1

积分
3
发表于 2022-12-10 17:24:37 | 显示全部楼层
这个是把foreach的过程变成流处理,transducer是将reduce的过程变成流处理。而且transducer主要适合动态语言,不适合有非擦除泛型。因为内部reduce的过程一般是先创建空列表然后往里面加东西,C++、C#静态语言要求在创建空列表的时候就指定类型(Java不需要),如果一开始就指定类型的话后面的map就不能变换类型了。
回复

使用道具 举报

0

主题

1

帖子

0

积分

新手上路

Rank: 1

积分
0
发表于 2022-12-10 17:25:24 | 显示全部楼层
嗯 我看了下transducer 相当于就是一个reducer的mapper 而且这个mapper是可以流式构建的从java的视角来看 这样的操作过于高阶了 毕竟+并不被认为是一个reducer 搞是可以搞 但我暂时没想到适合的应用场景
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|网资酷

GMT+8, 2025-7-6 11:03 , Processed in 0.082262 second(s), 22 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表