Clojure的懒惰序列: Kotlin中的相当物

5
Clojure提供了一种对(无限)序列进行惰性求值的方法。因此,只有当要使用它们时,才会计算这些值。
下面是一个由一个重复元素组成的无限序列示例:
(take 3 (repeat "Hello StackOverflow")) 
//=> ("Hello StackOverflow" "Hello StackOverflow" "Hello StackOverflow")

使用take有助于我们只消耗我们想要的序列元素。没有它,一个OutOfMemoryError会很快杀死进程。
另一个无限序列的例子如下:
(take 5 (iterate inc 1)) 
//(1 2 3 4 5)

或者更高级的序列提供阶乘函数:

((defn factorial [n]
   (apply * (take n (iterate inc 1)))) 5)

Kotlin是否提供类似的序列?它们是什么样子的?

为了在这里记录知识,我自己回答了这个问题。根据《我可以回答自己的问题吗?》,这是可以接受的。

1个回答

6
在Kotlin中,我们也可以利用Sequences进行惰性求值。为了创建一个序列,我们可以使用generateSequence(提供或不提供seed)。
fun <T : Any> generateSequence(
    seed: T?,
    nextFunction: (T) -> T?
): Sequence<T> (source)

Returns a sequence defined by the starting value seed and the function nextFunction, which is invoked to calculate the next value based on the previous one on each iteration.

以下将展示一些比较Clojure和Kotlin序列的示例。
1. 从一个静态值的无限序列中简单地取出take Clojure
(take 3 (repeat "Hello StackOverflow")) 

Kotlin

generateSequence { "Hello StackOverflow" }.take(3).toList()

这些内容非常相似。在Clojure中,我们可以使用repeat,而在Kotlin中则是使用静态值的generateSequence,该值将无限产生。在两种情况下,take被用于定义我们想要计算的元素数量。
注意:在Kotlin中,我们使用toList()将结果序列转换为列表。

2. 从一个无限动态值的序列中简单获取 take

Clojure

(take 5 (iterate inc 1))

Kotlin

generateSequence(1) { it.inc() }.take(5).toList()

这个示例有些不同,因为序列会无限制地产生前一个值的增量。Kotlin的generateSequence可以用种子(这里是1)和nextFunction(递增前一个值)调用。

3. 列表循环重复的值

Clojure

(take 5 (drop 2 (cycle [:first :second :third ])))
// (:third :first :second :third :first)

Kotlin

listOf("first", "second", "third").let { elements ->
    generateSequence(0) {
        (it + 1) % elements.size
    }.map(elements::get)
}.drop(2).take(5).toList()

在这个例子中,我们循环重复列表的值,删除前两个元素,然后取出5个。在Kotlin中,由于从列表中重复元素并不直观,因此这可能会变得冗长。为了解决这个问题,一个简单的扩展函数可以使相关代码更易读:
fun <T> List<T>.cyclicSequence() = generateSequence(0) {
    (it + 1) % this.size
}.map(::get)

listOf("first", "second", "third").cyclicSequence().drop(2).take(5).toList()

4. 阶乘

最后一个问题,让我们看看如何使用 Kotlin 序列来解决阶乘问题。首先,让我们回顾一下 Clojure 的版本:

Clojure

(defn factorial [n]
   (apply * (take n (iterate inc 1)))) 

我们从一个产生从1开始递增的数列中取n个值,并借助apply函数对它们进行累加。 Kotlin
fun factorial(n: Int) = generateSequence(1) { it.inc() }.take(n).fold(1) { v1, v2 ->
    v1 * v2
}

Kotlin提供了fold,可以让我们轻松地累加值。


特别是循环示例展示了Clojure在函数式编程上的更多专注。Kotlin有自己的强项:使用协程的命令式语法。我认为这是推荐复杂情况的惯用语,而不是强制在任何地方都使用FP:buildSequence { val items = listOf("first", "second", "third"); var i = 0; while (true) { yield(items[i++]); i %= items.size } } - Marko Topolnik
重点是,命令式语言非常容易扩展到任何复杂度,而无需每次都投入精力去寻找恰当的FP原语来定义,从而使您当前的逻辑看起来更简单。这些原语往往在整个代码库中只使用一次,但需要读者理解和记忆它们。 - Marko Topolnik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接