【问题标题】:Haskell's foldr equivalent in Java 8 [duplicate]Java 8中Haskell的foldr等价物[重复]
【发布时间】:2016-02-15 06:20:57
【问题描述】:

我们习惯于在 Haskell 中使用 foldr(例如,使用 Java 语法)List<T>,然后返回所需的任何类型(<T>List<T> 等)。

例如在 Haskell 中,这个函数接受 List<Integer> 并返回另一个 List<Integer> 并将 List<Integer> 用作累加器(只是一个例子,函数的目标无关紧要):

evens :: [Integer] -> [Integer]
evens = foldr (\ x acc -> if mod x 2 == 0 then x : acc else acc) []

现在 Java 8 已经推出并具有函数式特性,我们希望编写函数(不仅仅是 List<T> 的无重复等效项),其类型为 foldr我们在这里使用:

public static Double entropy (List<Double> probs){
    return -probs.stream().reduce(0.0, (acc, p) -> acc + p * Math.log(p, 2));
}

使用reduce的问题在于,当我们取List&lt;T&gt;时,我们只能返回&lt;T&gt;,而我们想要返回不同的类型甚至是集合。

有没有办法在 Java 8 中做foldr

【问题讨论】:

  • 您能否提供一个示例输入/输出以更好地了解需求?
  • 如果我正确地阅读了 Haskell(我从未做过 Haskell,但我可以尝试),似乎你只过滤偶数元素并将它们收集到一个列表中。这将是:probs.stream().filter(i -&gt; i % 2 == 0).collect(toList()).
  • @Tunaki 问题是我们需要一种像我们提供的示例中那样的累加器。
  • 那么这是一个副本:stackoverflow.com/q/29072355/1743880
  • 你检查过the other reduce method吗?

标签: java haskell java-8 reduce fold


【解决方案1】:

This method 似乎是最接近的对应物:

interface Stream<T> {
    // ...

    <U> U reduce(U identity,
                 BiFunction<U,? super T,U> accumulator,
                 BinaryOperator<U> combiner)

    // ...
}

不过,它更像是 Haskell 的 foldMapfoldl' 的混合体,而不是从右侧折叠。对于像 Java 这样的热切评估语言来说,这可能是更好的选择 - 具有热切评估的左折叠始终在恒定空间中运行,而具有热切评估的右折叠在线性空间中运行 - 右折叠长列表可能会破坏堆栈。

在 Haskell 中,由于它具有惰性求值,因此具有对列表脊椎不严格的函数的右折叠通常在线性空间中运行。例如,在find 的以下实现中利用了这一点,它不必评估它返回的元素之后的折叠:

find :: (a -> Bool) -> [a] -> Maybe a
find p = foldr go Nothing
   where go a next
     | p a = Just a     -- discards `next` without evaluating it
     | otherwise = next

但是在像 Scheme 这样的急切语言中应该避免这样的实现,在这些语言中你要避免在这样的函数中使用折叠,因为你希望它们提前退出:

(define (find pred? as)
  (and (not-null? as)
       (let ((head (car as)))
         (if (pred? head)
             head
             (find pred? (cdr as))))))

另一件事是,Java 流也旨在支持并行性——因此它的操作被设计为可以无序访问元素(因此 Javadoc 坚持关联操作)。

【讨论】:

    【解决方案2】:

    我不擅长 Java 8,但我认为解决方案的关键在于 Haskell 如何根据 Foldablefold 提供默认的 foldr

    foldr :: (a -> b -> b) -> b -> t a -> b
    foldr f z t = appEndo (foldMap (Endo . f) t) z
    
    foldMap :: (Foldable t, Functor t, Monoid m) => (a -> m) -> t a -> m
    foldMap f = fold . fmap f
    
    fold :: (Foldable t, Monoid m) => t m -> m
    

    其中Endo a 只是合成下的内同态幺半群。

    因此,一个解决方案可能是使用Function&lt;B,B&gt; 作为Endo b 的类似物, 并将Function::compose 作为累加器传递给T reduce(T identity, BinaryOperator<T> accumulator)

    // given
    //  BiFunction<A,B,B> step
    //  B start
    //  Stream<A> stream
    stream                                            // Stream<A>
      .map((a) -> (b) -> step.apply(a,b))             // Stream<Function<B,B>>
      .reduce(Function.identity(), Function::compose) // Function<B,B>
      .apply(start)                                   // B
    

    但我目前没有 Java 8 编译器,所以无法测试。

    如果我们想使用foldl,解决方案类似,只是我们使用Function::andThen

    // given
    //  BiFunction<B,A,B> step
    //  B start
    //  Stream<A> stream
    stream                                            // Stream<A>
      .map((a) -> (b) -> step.apply(b,a))             // Stream<Function<B,B>>
      .reduce(Function.identity(), Function::andThen) // Function<B,B>
      .apply(start)                                   // B
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-19
      • 1970-01-01
      • 2011-12-15
      • 2011-05-13
      • 2011-01-28
      • 2019-03-16
      • 2014-02-25
      • 2023-03-04
      相关资源
      最近更新 更多