为测量数据结构的函数提供什么类型? Int,Integer,Integral?

时间:2018-01-27 15:40:54

标签: haskell

我有一个数据结构,例如表达式树或图形。我想添加一些"测量"功能,例如depthsize

如何最好地输入这些功能?

我看到以下三种变体具有大致相同的用途:

  • depth :: Expr -> Int
  • depth :: Expr -> Integer
  • depth :: Num a => Expr -> a

我有以下注意事项:

  • 我以basefgl为例,他们一直使用Int,但Data.List也有genericLength等功能返回类型中的多态性,我认为这些generic函数的添加可能是现代化趋势的反映,我可能应该尊重和强化。

  • 当一些广泛使用的库提供一组具有相同功能的全面功能时,类似的思维运动是显而易见的,当用户需要几种可能的返回类型选择时(例如{{1}提供接受xml-conduitByteString的惰性和严格类型的解析器。

  • Text是一个比Integer更好的类型,我有时会发现我需要将一个列表的长度转换为Int,这是因为运算的algorighm在Integer中需要考虑这个长度。

  • 使函数返回Integer意味着这些函数变为多态,并且可能会降低性能。我不太了解所有细节,但据我所知,可能会有一些运行时成本,而且多态事物更难记忆。

什么是最受欢迎的做法?哪个部分是由于遗留和兼容性考虑因素? (即如果今天设计IntegralData.List等功能会有哪些类型?)我是否错过了任何利弊?

2 个答案:

答案 0 :(得分:6)

简短回答:作为一般规则使用Int,如果您需要将其转换为其他内容,请使用fromIntegral。 (如果您发现自己进行了很多转换,请定义fi = fromIntegral以保存输入,或者创建自己的包装器。)

主要考虑因素是表现。您想编写算法,以便在内部使用有效的整数类型。如果Int足够大,无论你做什么计算(标准保证有符号的30位整数,但即使在使用GHC的32位平台上,它是一个带符号的32位整数),你可以假设它将是平台上的高速整数类型,特别是与Integer(具有无法优化的拳击和bignum计算开销)相比较。请注意,性能差异可能很大。与Int相比,使用Integer s的简单计数算法通常会快5-10倍。

虽然可以为您的功能提供不同的签名:

depth :: Expr -> Integer
depth :: (Num a) => Expr -> a

但实际上是使用高效的Int类型在引擎盖下实现它并在最后进行转换,这使得转换隐含在我看来是不好的做法。特别是如果这是一个库函数,通过使其成为签名的一部分,明确表示内部使用Int会让我更加明智。

关于您列出的注意事项:

首先,generic*中的Data.List函数不是现代的。特别是,genericLength在1996年7月发布的GHC 0.29中提供了length。在之前的某个时间点,genericLength已根据 length :: [a] -> Int length = genericLength 定义 ,简单地说:

#ifdef USE_REPORT_PRELUDE

但是在GHC 0.29中,这个定义用length注释掉了,generic*的几个手动优化变体是独立定义的。其他Prelude函数不是0.29,但GHC 4.02(1998)已经存在。

最重要的是,当length Foldable版本的genericLength从列表推广到ByteString时,哪个 是一个相当近期的开发(自GHC 7.10起? ),没有人关心Text做任何事情。我也不认为我曾经在任何严肃的Haskell代码中看到过“在野外”使用过的这些函数。在大多数情况下,您可以将它们视为已弃用。

其次,在库中使用lazy / strict和conduit-xml / ByteString变体代表了一种不同的情况。特别是,Text用户通常会根据有关正在处理的数据的考虑因素以及算法的构造,在惰性和严格变体之间以及conduit-xmlText类型之间做出决定。对于给定程序的整个类型系统而言,它具有深远的影响力。如果将ByteString与惰性Text类型一起使用的唯一方法是将其零碎地转换为严格Int s,则将其传递给库,然后将其拉出并将其转换回来对于懒惰的depth类型,没有人会接受这种复杂性。相比之下,fromInteger . depth的单态Integer定义可以正常工作,因为您需要depth来使其适应任何数字上下文。

第三,如上所述,count只是在你不关心性能的情况下具有任意精度的观点,它只是一种“更好”的类型。对于Data.ListInt这样的实际情况,性能可能比无限精度更重要。

第四,我不认为在这里选择多态或非多态版本时,运行时成本或失败记忆应该是严肃的考虑因素。在大多数情况下,GHC将在memoization没有问题的上下文中生成多态函数的专用版本。

在此基础上,我怀疑如果今天设计{{1}},它仍会使用{{1}}。

答案 1 :(得分:3)

我同意K. A. Buhr的所有观点,但这里有更多的答案:

如果您希望支持某种不适合内存的表达式树(这似乎很有趣,但不太可能),您应该使用返回类型Integer。如果我看到Expr -> Integer,我会查看代码或文档,试图了解codomain如何或为何如此大。

重新。性能Integer:如果数字不大于机器字的最大宽度,将使用正常的机器字算法。简化,类型基本上是:

data Integer = SmallInteger Int | LargeInteger ByteArray

ķ。 A. Buhr提到存在一个不可避免的性能损失,即该值不能被取消装箱(即它将始终具有堆表示,并且将被读取并写入存储器),这听起来对我来说是正确的。< / p>

相比之下,Int(或Word)上的功能通常是未装箱的,因此在核心中您会看到类似Int# -> Int# ->的类型。您可以将Int#视为仅存在于机器寄存器中。如果您关心性能,这就是您希望数字代码的样子。

重新。多态版本:根据方便的类型推断,围绕具体的数字输入和多态数字输出设计库可能没问题。我们已经在某种程度上已经有了这个,因为数字文字被重载了。有些时候文字(例如,当-XOverloadedStrings时也需要字符串文字)需要给出类型签名,所以我希望如果base设计为更多态,你会遇到更多需要这种情况的场合(fromIntegral的用途较少)。

您未提及的另一个选项是使用Word来表示depth是非负面的。这对输入更有用,但即使这样也常不值得:Word仍会溢出,负面文字有效(尽管GHC会发出警告);在某种程度上,它只是在错误发生的地方移动。