data/collection 中的球拍序列与内置序列

Racket sequences in data/collection vs built-in sequences

我一直在玩 data/collection 中的一些界面,到目前为止我很喜欢它。具有不同 Racket 集合(如列表、流和序列)的通用接口非常方便——特别是考虑到这些类型的接口的多样性,否则(list-*vector-*string-*stream-*, sequence-*, ... !).

但是这些接口是否与 Racket 中的内置序列配合得很好?具体来说,我 运行 遇到了这个错误:

(require data/collection)
(take 10 (in-cycle '(1 2 3)))

=>

; take: contract violation
;   expected: sequence?
;   given: #<sequence>
;   in: the 2nd argument of
;       (-> natural? sequence? sequence?)
;   contract from: 
;       <pkgs>/collections-lib/data/collection/sequence.rkt
;   blaming: top-level
;    (assuming the contract is correct)
;   at: <pkgs>/collections-lib/data/collection/sequence.rkt:53.3

函数 in-cycle returns 是内置的 "sequence," 而 data/collections 提供的多态 take 需要它自己的特殊序列接口。

在这种特殊情况下,我可以手动定义一个流来替换内置的 in-cycle,例如:

(define (in-cycle coll [i 0])
  (stream-cons (nth coll (modulo i (length coll)))
               (in-cycle coll (add1 i))))

... 这行得通,但是有一个 awful lot of built-in sequences defined 所以我想知道是否有更好的方法,也许 standard/recommended 来处理这个问题。也就是说,我们是否可以根据 data/collection 中定义的序列来利用所有内置序列,就像后者包装其他现有序列(如列表和流)一样?

正如@Sorawee Porncharoenwase 提到的,您可以使用 data/collection 中的 cycle 而不是 built-in in-cycle

您还可以将 sequence->stream 应用于 in-cycle 的结果, 因为球拍的流既是 built-in 又是 data/collection 序列。例如,

(take 10 (sequence->stream (in-cycle '(1 2 3 4))))

有点棘手。此表达式 (in-cyle '(1 2 3)) 的计算结果为 Racket 序列。 球拍序列不同于 "generic sequences"(请参阅 data/collection 的文档)。

当您从 data/collection 请求 take 时,您会得到需要通用集合的 take,因此

#lang racket
(require data/collection)
(take 10 (in-cycle '(1 2 3)))

会给你一个错误。

文档说以下内置数据类型可用作集合:

  • 列表
  • 不可变哈希表
  • 不可变向量
  • 不可变哈希集
  • 不可变字典

所以我们需要将序列(in-cycle '(1 2 3))转换成上面的其中一种。 @capfredf 提到的明显选择是 sequence->stream.

#lang racket
(require data/collection)
(take 10 (sequence->stream (in-cycle '(1 2 3))))

这按预期工作。

在进一步研究之后,我想我对 Racket 和 data/collection 中的序列有了更好的理解。我会尽力总结其他答案和评论中提出的所有要点,并包括我自己的学习。

球拍序列,即来自 built-in 的序列,旨在成为所有有序集合的 generic interface,就像您可以使用 dict-* 函数来使用任何字典类型,包括哈希。此外,还有许多方便的实用程序提供内置序列,以便在不同场景中轻松处理有序数据,例如从集合中获取的元素序列,或在某个输入端口接收到的输入序列,或序列从字典中提取的 key-value 对——最后一个本质上不是 "ordered" 集合,但可以通过使用 built-in 序列接口将其视为一个集合。

因此我们可以认为内置序列具有双重目的:

  1. 作为有序数据的统一接口,并且
  2. 通过在每种情况下提供自然的序列接口实现,方便在不同场景中使用序列。

现在,虽然内置序列在理论上旨在成为有序集合的统一接口,但在实践中,由于它们的冗长,例如,它们并不是特别适用于此目的。 sequence-takesequence-length 而不仅仅是我们用于列表的 takelength

data/collection 序列解决了这个缺点,因为它们的名称简短且规范,例如 take 而不是 sequence-take。此外,这些序列还为内置序列提供的许多序列实用程序提供 drop-in replacements,例如 cyclenaturals 而不是 in-cyclein-naturals,以及使用通用 in 函数来导出任何序列的惰性版本以用于迭代(如 (in (naturals)))。由于不可变,这些 data/collection 版本通常更多 "well-behaved",内置序列不能保证。因此,在许多情况下,data/collection 序列可以被认为是内置序列的 替代品 ,这在很大程度上接管了内置序列的两个目的中的第一个。

也就是说,在你处理序列的地方,考虑使用data/collection序列代替内置序列,而不是作为一种方式使用 内置序列。

然而,关于第 (2) 点,以下是当前可作为 data/collection 序列处理的类型:

  • 列表
  • 不可变哈希表
  • 不可变向量
  • 不可变哈希集
  • 不可变字典

(source)

这已经足够了,但还有更多场景可以推导出 common-sense 序列。对于以上未涵盖的任何此类情况,内置序列实用程序仍然有用,例如 in-hashin-portdata/collection 序列中没有类似物。通常,在许多情况下,我们可以轻松导出内置序列(请参阅实用程序 here),但不能导出 data/collection 序列。在这些特殊情况下,我们可以简单地将如此获得的内置序列通过 sequence->stream 转换为流,然后通过更简单的 data/collection 序列接口使用它,因为流可以被视为任何一种类型的序列。