【问题标题】:Are Clojure transducers the same concept as intermediate operations on streams in Java?Clojure 转换器与 Java 中流的中间操作的概念是否相同?
【发布时间】:2016-02-01 17:57:53
【问题描述】:

当我学习 Clojure 中的转换器时,我突然想到了它们让我想起的东西:Java 8 流!

Transducers are composable algorithmic transformations. They are independent from the context of their input and output sources and specify only the essence of the transformation in terms of an individual element.

A stream is not a data structure that stores elements; instead, it conveys elements from a source such as a data structure, an array, a generator function, or an I/O channel, through a pipeline of computational operations.

Clojure:

(def xf
  (comp
    (filter odd?)
    (map inc)
    (take 5)))

(println
  (transduce xf + (range 100)))  ; => 30
(println
  (into [] xf (range 100)))      ; => [2 4 6 8 10]

Java:

// Purposely using Function and boxed primitive streams (instead of
// UnaryOperator<LongStream>) in order to keep it general.
Function<Stream<Long>, Stream<Long>> xf =
        s -> s.filter(n -> n % 2L == 1L)
                .map(n -> n + 1L)
                .limit(5L);

System.out.println(
        xf.apply(LongStream.range(0L, 100L).boxed())
                .reduce(0L, Math::addExact));    // => 30
System.out.println(
        xf.apply(LongStream.range(0L, 100L).boxed())
                .collect(Collectors.toList()));  // => [2, 4, 6, 8, 10]

除了静态/动态类型的不同之外,这些在目的和用法上似乎与我非常相似。

类比 Java 流的转换是考虑转换器的合理方式吗?如果不是,它有什么缺陷,或者两者在概念上有何不同(更不用说实现了)?

【问题讨论】:

  • 在我未经训练的眼睛看来,它是这样的。但是他们对换能器有如此高的重视,也许还有更多? :)
  • 语义相似性比比皆是!

标签: java clojure java-8 java-stream transducer


【解决方案1】:

主要区别在于,动词(操作)集以某种方式对流关闭,而对转换器开放:例如尝试在流上实现@​​987654321@,感觉有点二等:

import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.Stream.Builder;

public class StreamUtils {
    static <T> Stream<T> delay(final Supplier<Stream<T>> thunk) {
        return Stream.of((Object) null).flatMap(x -> thunk.get());
    }

    static class Partitioner<T> implements Function<T, Stream<Stream<T>>> {
        final Function<T, ?> f;

        Object prev;
        Builder<T> sb;

        public Partitioner(Function<T, ?> f) {
            this.f = f;
        }

        public Stream<Stream<T>> apply(T t) {
            Object tag = f.apply(t);
            if (sb != null && prev.equals(tag)) {
                sb.accept(t);
                return Stream.empty();
            }
            Stream<Stream<T>> partition = sb == null ? Stream.empty() : Stream.of(sb.build());
            sb = Stream.builder();
            sb.accept(t);
            prev = tag;
            return partition;
        }

        Stream<Stream<T>> flush() {
            return sb == null ? Stream.empty() : Stream.of(sb.build());
        }
    }

    static <T> Stream<Stream<T>> partitionBy(Stream<T> in, Function<T, ?> f) {
        Partitioner<T> partitioner = new Partitioner<>(f);
        return Stream.concat(in.flatMap(partitioner), delay(() -> partitioner.flush()));
    }
}

与序列和归约器一样,当您进行转换时,您不会创建“更大”的计算,而是创建“更大”的源。

为了能够传递计算,您引入了 xf 一个从 Stream 到 Stream 的函数,以将操作从方法提升到一流的实体(以便从源头上解开它们)。通过这样做,您创建了一个转换器,尽管接口太大。

下面是上述代码的更通用版本,用于将任何 (clojure) 转换器应用于流:

import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.Stream.Builder;

import clojure.lang.AFn;
import clojure.lang.IFn;
import clojure.lang.Reduced;

public class StreamUtils {
    static <T> Stream<T> delay(final Supplier<Stream<T>> thunk) {
        return Stream.of((Object) null).flatMap(x -> thunk.get());
    }

    static class Transducer implements Function {
        IFn rf;

        public Transducer(IFn xf) {
            rf = (IFn) xf.invoke(new AFn() {
                public Object invoke(Object acc) {
                    return acc;
                }

                public Object invoke(Object acc, Object item) {
                    ((Builder<Object>) acc).accept(item);
                    return acc;
                }
            });
        }

        public Stream<?> apply(Object t) {
            if (rf == null) return Stream.empty();
            Object ret = rf.invoke(Stream.builder(), t);
            if (ret instanceof Reduced) {
                Reduced red = (Reduced) ret;
                Builder<?> sb = (Builder<?>) red.deref();
                return Stream.concat(sb.build(), flush());
            }
            return ((Builder<?>) ret).build();
        }

        Stream<?> flush() {
            if (rf == null) return Stream.empty();
            Builder<?> sb = (Builder<?>) rf.invoke(Stream.builder());
            rf = null;
            return sb.build();
        }
    }

    static <T> Stream<?> withTransducer(Stream<T> in, IFn xf) {
        Transducer transducer = new Transducer(xf);
        return Stream.concat(in.flatMap(transducer), delay(() -> transducer.flush()));
    }
}

【讨论】:

  • 先生,我可以多说几句吗?
  • @ArthurUlfeldt 更多的话,不确定是否更好:)
  • 确实更好!它表明,它们仅在很少或没有还原上下文的琐碎情况下是相同的。我怀疑这就是 glts 所要求的。
【解决方案2】:

我看到的另一个重要区别是 Clojure 转换器是可组合的。我经常遇到这样的情况,即我的流管道比您的示例中的要长一些,其中只有一些中间步骤可以在其他地方重复使用,例如:

someStream
   .map(...)
   .filter(...)
   .map(...)      // <- gee, there are at least two other
   .filter(...)   // <- pipelines where I could use the functionality
   .map(...)      // <- of just these three steps!
   .filter(...)
   .collect(...)

我还没有找到实现这一目标的合理方法。我希望我是这样的:

Transducer<Integer,String> smallTransducer = s -> s.map(...); // usable in a stream Integer -> String
Transducer<String,MyClass> otherTransducer = s -> s.filter(...).map(...); // stream String -> MyClass
Transducer<Integer,MyClass> combinedTransducer = smallTransducer.then(otherTransducer); // compose transducers, to get an Integer -> MyClass transducer

然后像这样使用它:

someStream
   .map(...)
   .filter(...)
   .transduce(smallTransducer)
   .transduce(otherTransducer)
   .filter(...)
   .collect(...)

// or

someStream
   .map(...)
   .filter(...)
   .transduce(combinedTransducer)
   .filter(...)
   .collect(...)

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2017-10-12
  • 2020-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-15
  • 1970-01-01
  • 2015-06-14
相关资源
最近更新 更多