有没有办法分开无限列表和有限列表?

Posted

技术标签:

【中文标题】有没有办法分开无限列表和有限列表?【英文标题】:Is there any way to separate infinite and finite lists? 【发布时间】:2016-01-06 02:41:39 【问题描述】:

例如,我正在为列表编写一些函数,我想使用长度函数

foo :: [a] -> Bool
foo xs = length xs == 100

有人怎么能理解这个函数是否可以与无限列表一起使用?

或者我应该总是考虑无限列表并使用类似的东西

foo :: [a] -> Bool
foo xs = length (take 101 xs) == 100

而不是直接使用长度?

如果 haskell 有 FiniteList 类型,那么 length 和 foo 会怎样

length :: FiniteList a -> Int
foo :: FiniteList a -> Bool

【问题讨论】:

【参考方案1】:

length 遍历整个列表,但要确定列表是否具有特定长度 n,您只需查看第一个 n 元素。

您使用take 的想法会奏效。或者 您可以像这样编写lengthIs 函数:

-- assume n >= 0
lengthIs 0 [] = True
lengthIs 0 _  = False
lengthIs n [] = False
lengthIs n (x:xs) = lengthIs (n-1) xs

您可以使用相同的想法编写lengthIsAtLeastlengthIsAtMost 变体。

【讨论】:

嗯 - 你希望length 无限列表返回什么? ais:您可能对this discussion of data and codata 感兴趣。使用这个术语 length 应该只接受数据,但列表可能是 codata。 您可以在 Haskell 中定义 FiniteList 类型 - 例如 Vector a 可能适用于您的用例。事实上,您在 Python、Perl、Ruby 等语言中看到的列表确实最好由 Haskell 中的 Vector a 建模。列表可能是无限的,这在 Haskell 中非常有用,当您了解有关 Haskell 的更多信息时,您将了解何时适合使用列表以及何时应该使用不同的数据结构。 @user3237465,这就是“懒惰的Nat”方法的效果。但是length 产生Int,并且没有无限Int 这样的东西。也没有无限自然数之类的东西,但懒惰有效地将“无限”S $ S $ S $ ... 与自然数相邻。 @dfeuer,我的意思是“长度”——比length 更笼统。有一个无限的自然数,在 Haskell 中 NatConat。我不建议在任何地方都使用Nats,但是一个无限长的无限列表对我来说是完全明智的构造。【参考方案2】:

编辑时:我主要回答您标题中的问题,而不是您的特定示例的细节(ErikR 的回答非常好)。

列表上的许多函数(例如length 本身)仅对有限列表有意义。如果您编写的函数仅对有限列表有意义,请在文档中明确说明(如果不明显)。由于停止问题无法解决,因此没有任何方法可以强制执行该限制。根本没有算法可以提前确定是否理解

takeWhile f [1..]

(其中f 是整数谓词)生成有限或无限列表。

【讨论】:

好点!但是有一种方法可以区分“绝对有限”和“可能无限”的列表。【参考方案3】:

Nats 和懒惰再次来袭:

import Data.List

data Nat = S Nat | Z deriving (Eq)

instance Num Nat where
    fromInteger 0 = Z
    fromInteger n = S (fromInteger (n - 1))

    Z   + m = m
    S n + m = S (n + m)

lazyLength :: [a] -> Nat
lazyLength = genericLength

main = do
    print $ lazyLength [1..]    == 100 -- False
    print $ lazyLength [1..100] == 100 -- True

【讨论】:

效率如何? ;) @Michael,我猜比直接实现慢了一个小的常数因子。但是你不需要编写lengthIsAtLeastlengthIsAtMost 和其他花哨的函数——Nat 模块会免费提供它们。但是,AFAIK,没有这样的模块,所以最好只使用lengthIs 好吧,在我的计算机上,使用 5000000 而不是 100,我有 1.291s 用于 Nat 实现和 0.185s 用于幼稚的 isLength 实现。两者都使用ghc -O2 编译。这是一个 6.9 的因数……如果我将最大值增加到 10000000,这个因数保持不变。 @Michael,将lazyLength 直接定义为lazyLength = foldr (\_ -> S) Z 给我4.96s 对应lengthIs 1000000005.74 对应lazyLength [1..100000000] == 100000000 genericLength 是严格执行还是什么?或者为什么它比等效的 foldr 实现慢?【参考方案4】:

ErikR 和 John Coleman 已经回答了您问题的主要部分,但我想补充一点:

最好以不依赖于输入的有限性或无限性的方式编写函数 - 有时这是不可能的,但很多时候只是重新设计的问题。例如,您可以计算一个运行平均值,而不是计算整个列表的平均值,它本身就是一个列表;如果输入列表是无限的,那么这个列表本身就是无限的,否则就是有限的。

avg :: [Double] -> [Double]
avg = drop 1 . scanl f 0.0 . zip [0..]
  where f avg (n, i) = avg * (dbl n / dbl n') +
                       i            / dbl n'      where n'  = n+1
                                                        dbl = fromInteger

在这种情况下,您可以平均一个无限列表,而不必取其length

*Main> take 10 $ avg [1..]
[1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0]

换句话说,一种选择是将尽可能多的函数设计为根本不关心无穷大方面,并将列表的(完整)评估和其他(可能是无限的)数据结构延迟到最后一个阶段尽可能在你的程序中。

通过这种方式,它们也将更具可重用性和可组合性——任何对其输入的一般假设更少或更多的东西往往更具可组合性;相反,任何具有更多或更多特定假设的东西往往不太可组合,因此可重用性较差。

【讨论】:

如果你的函数不依赖于有限性,那么像长度这样的函数是没有用的。您可以使用它们,但随后您意识到您需要重写所有代码。 我的意思是你可以重新设计你的代码,它不依赖于length——当然,在一定程度上,但如果你可以这样做,它会让你的代码更灵活,并且功能更不受输入的某些方面的影响。 我的代码很简单,我想知道列表的长度是否为 100。所以我写了length xs == 100,然后我意识到由于列表无限,我不能这样做。 我的建议很笼统。也许你现在的项目只是一个学习的玩具,length xs == 100 是你的终极目标,但未来可能不是这样。【参考方案5】:

有几种不同的方法可以创建有限列表类型。第一个是简单地使列表在其脊椎骨中变得严格:

data FList a = Nil | Cons a !(FList a)

不幸的是,这抛弃了懒惰带来的所有效率优势。其中一些可以通过使用长度索引列表来恢复:

-# LANGUAGE GADTs #-
-# LANGUAGE DataKinds #-
-# LANGUAGE KindSignatures #-
-# LANGUAGE ScopedTypeVariables #-
-# OPTIONS_GHC -fwarn-incomplete-patterns #-

data Nat = Z | S Nat deriving (Show, Read, Eq, Ord)

data Vec :: Nat -> * -> * where
  Nil :: Vec 'Z a
  Cons :: a -> Vec n a -> Vec ('S n) a

instance Functor (Vec n) where
  fmap _f Nil = Nil
  fmap f (Cons x xs) = Cons (f x) (fmap f xs)

data FList :: * -> * where
  FList :: Vec n a -> FList a

instance Functor FList where
  fmap f (FList xs) = FList (fmap f xs)

fcons :: a -> FList a -> FList a
fcons x (FList xs) = FList (Cons x xs)

funcons :: FList a -> Maybe (a, FList a)
funcons (FList Nil) = Nothing
funcons (FList (Cons x xs)) = Just (x, FList xs)

-- Foldable and Traversable instances are straightforward
-- as well, and in recent GHC versions, Foldable brings
-- along a definition of length.

GHC 不允许无限类型,因此无法构建无限 Vec,因此无法构建无限 FList (1)。但是,FList 可以稍微懒惰地转换和使用,从而带来缓存和垃圾收集的好处。

(1) 请注意,类型系统强制 fcons 在其 FList 参数中为 strict,因此任何与 FList 打结的尝试都会触底。

【讨论】:

以上是关于有没有办法分开无限列表和有限列表?的主要内容,如果未能解决你的问题,请参考以下文章

Scala,无限重复有限列表

无限计数器的无限列表

如何使用有限的数组在React Native中使平面列表无限滚动

React Material UI 多重折叠

有没有办法让多个线程顺序访问共享资源列表?

Python asyncio:处理潜在的无限列表