将Haskell函数转换为java“函数”

时间:2017-11-14 21:11:35

标签: java haskell java-8 java-stream

我在Haskell中有这个函数,我想知道如何将它转换为Java,特别是使用流:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

1 个答案:

答案 0 :(得分:6)

(我不是Haskell专家,但我知道这很危险。)

给出的示例代码有几个合理映射的Haskell结构 以及Java构造:

  • Haskell列表很懒,所以它对应于Java Stream。

  • 使用的范围是整数,因此它们对应于IntStream。 例如,[240..1280]对应IntStream.rangeClosed(240, 1280)

  • 带有步骤的范围在Java中没有直接对应关系,但它很容易 计算;你只需要做一些算术然后映射值 从顺序范围到步骤范围。例如,[2, 4..20] 可以写成

    IntStream.rangeClosed(1, 10).map(i -> 2 * i)
    
  • 列表推导的条件对应于过滤流 谓词。

  • 使用多个生成器的理解对应于flatmapping 嵌套流。

  • 在Java中没有通用的方法来表示元组。各种第三方 库提供了有关的各种权衡的元组实现 仿制药和拳击。或者,您可以使用字段编写自己的类 你要。 (如果你使用很多不同类型的话,这可能会非常繁琐 但是,元组。)在这种情况下,元组只是四个整数,所以很容易 使用带有四个元素的int数组表示。

总而言之,我们得到以下结论。

static Stream<int[]> build() {
    return IntStream.rangeClosed(240, 1280).boxed()
               .flatMap(w -> IntStream.rangeClosed(1, 10).map(m -> 2 * m).boxed()
                   .flatMap(m -> IntStream.rangeClosed(2, 100).boxed()
                       .flatMap(n -> IntStream.rangeClosed(240, 1280)
                                              .filter(g -> ((w - 2*m - n*g) % (n+1) == 0))
                                              .filter(g -> n*g+2*m <= w)
                                              .filter(g -> n*g <= w)
                                              .mapToObj(g -> new int[] { w, m, n, g }))));
}

与原始的Haskell相比,这显然非常冗长,但您可以很容易地看到Haskell结构在Java代码中的最终位置。我相信这是正确的,因为它似乎生成与Haskell代码相同的输出。

请注意,我们使用IntStream生成值,但我们希望flatmap提供数组流(对象),而IntStream.flatMap返回IntStream。也许理想情况下会有flatMapToObj操作,但没有,所以我们必须将int值插入Integer对象,然后调用它Stream.flatMap

可以将流管道分配给Stream类型的变量,但这不是很方便,因为Java流最多可以使用一次。由于构造这样的流很便宜(与评估它相比),编写一个函数build()是合理的,该函数返回一个新创建的流,准备由调用者进行评估。

运行以下Java代码时,

    System.out.println(build().count());
    System.out.println(build().findFirst().map(Arrays::toString).orElse("not found"));
    System.out.println(build().reduce((a, b) -> b).map(Arrays::toString).orElse("not found"));

结果是:

654559
[484, 2, 2, 240]
[1280, 20, 5, 248]

运行以下Haskell代码(build的定义从问题中复制)

build = [(w,m,n,g) | w <- [240..1280], m <- [2,4..20], n <- [2..100], g <- [240..1280],
                     ((w - 2*m - n*g) `mod` (n+1) == 0), n*g+2*m <= w, n*g <= w]

main = do
    print (length build)
    print (head build)
    print (last build)

给出以下输出:

654559
(484,2,2,240)
(1280,20,5,248)

因此,我的眼睛看起来是正确的音译。

head(Java,findFirst)和last(Java,reduce((a, b) -> b))操作的时间如下:(使用GHC 7.6.3更新 - O2)

       head     last
GHC      8s      36s
JDK      3s       9s

这至少表明两个系统都提供了懒惰,因为计算在找到第一个元素后被短路,而找到最后一个元素则需要计算所有元素。

有趣的是,在Haskell中,调用lengthheadlast中的所有三个不会花费更多时间而不仅仅是调用last(大约36秒)因为记忆。 Java中没有任何memoization,但当然你可以将结果显式存储在一个数组或List中并多次处理。

总的来说,我对Java实现的速度有多惊讶。我并不真正理解Haskell的性能,所以我将把它留给Haskell专家来评论。我很可能做错了,虽然大多数情况下我只是将问题中的函数复制到文件中并使用GHC进行编译。

我的环境:

JDK 9,GHC 7.6.3 -O2,MacBook Pro 2014年中期2核3GHz英特尔酷睿i7