有没有办法分开无限列表和有限列表?
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
您可以使用相同的想法编写lengthIsAtLeast
和lengthIsAtMost
变体。
【讨论】:
嗯 - 你希望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 中 Nat
≡ Conat
。我不建议在任何地方都使用Nat
s,但是一个无限长的无限列表对我来说是完全明智的构造。【参考方案2】:
编辑时:我主要回答您标题中的问题,而不是您的特定示例的细节(ErikR 的回答非常好)。
列表上的许多函数(例如length
本身)仅对有限列表有意义。如果您编写的函数仅对有限列表有意义,请在文档中明确说明(如果不明显)。由于停止问题无法解决,因此没有任何方法可以强制执行该限制。根本没有算法可以提前确定是否理解
takeWhile f [1..]
(其中f
是整数谓词)生成有限或无限列表。
【讨论】:
好点!但是有一种方法可以区分“绝对有限”和“可能无限”的列表。【参考方案3】:Nat
s 和懒惰再次来袭:
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,我猜比直接实现慢了一个小的常数因子。但是你不需要编写lengthIsAtLeast
、lengthIsAtMost
和其他花哨的函数——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 100000000
和5.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
打结的尝试都会触底。
【讨论】:
以上是关于有没有办法分开无限列表和有限列表?的主要内容,如果未能解决你的问题,请参考以下文章