Haskell回避概率数据结构?

Haskell回避概率数据结构?,haskell,structure,Haskell,Structure,如果您搜索Haskell中实现的跳过列表,您将找不到很多。它是一种概率数据结构,需要一个随机数生成器,这意味着这些结构中的任何一个都需要在IO monad中运行 Haskell的人是否因为不可能完全实现这些数据结构而远离这些数据结构?哈斯凯尔怎么对付他们 首先,IO单子中的随机数生成器是为了方便起见。您可以在IO monad之外使用随机数生成器;见System.Random。但是,是的,你确实需要保持状态;圣莫纳德在这里很有用。是的,我想说Haskell程序员更喜欢纯数据结构。因为SkipLis

如果您搜索Haskell中实现的跳过列表,您将找不到很多。它是一种概率数据结构,需要一个随机数生成器,这意味着这些结构中的任何一个都需要在IO monad中运行


Haskell的人是否因为不可能完全实现这些数据结构而远离这些数据结构?哈斯凯尔怎么对付他们

首先,IO单子中的随机数生成器是为了方便起见。您可以在IO monad之外使用随机数生成器;见System.Random。但是,是的,你确实需要保持状态;圣莫纳德在这里很有用。是的,我想说Haskell程序员更喜欢纯数据结构。

因为SkipList有一个纯接口,所以可以在内部使用
IO
实现,并为接口使用
unsafePerformIO
。这只是将“正确使用”的负担从语言转移到程序员身上(这是不纯正语言的负担)。

伪随机数生成器当然可以在
IO
之外使用,通过简单地存储当前生成器值以及概率纯数据结构,并在构建修改版本时更新它。这样做的缺点是,PRNG将比不纯程序更明显地具有确定性,因为单一数据结构之外的任何内容都不会对其进行更新。如果只有统计特性是重要的,那么这不会引起任何问题,但在其他方面可能会引起关注

另一方面,隐藏一个不纯的PRNG可以说是合理使用
unsafePerformIO
,就像Ganesh Sittampalam的回答一样。这公然违反了引用透明性,但仅限于PRNG将返回不可预测、不一致的值的程度——这就是关键所在!然而,仍然需要小心,因为编译器可能会对代码做出错误的假设,因为它看起来很纯

但实际上,这两种方法都不太吸引人。使用
unsafePerformIO
是不令人满意的,并且有潜在的危险。线程化PRNG状态很容易,但会对使用它的任何计算施加(可能是虚假的)严格排序。Haskell程序员不会轻易放弃安全和懒惰(这是正确的!),当然,仅限于
IO
的数据结构的实用性有限。所以,为了回答您的部分问题,这就是为什么Haskell程序员可能会避免使用这种结构


至于“哈斯克尔如何处理”这样的事情,我认为这是一个错误的问题

实际上,许多数据结构和算法都隐含地假设(并优化)一种命令式的、不纯净的、严格的语言,虽然可以在Haskell中实现这些语言,但这几乎是不可取的,因为(甚至忽略了内部实现)使用它们会给代码带来一种非常不习惯的结构和方法。此外,由于Haskell违反了这些隐含的假设,性能往往会降低(有时甚至会严重降低)

需要认识到的是,算法和数据结构只是一种手段,而不是目的。很少需要一个特定的实现——所需的通常是某些性能特征。找到既能提供所需特性又能惯用Haskell的数据结构/算法几乎总是一个更好的计划,而且可能比试图将严格的命令性peg塞进懒惰的函数洞要好


这种错误可能最常见于那些从未遇到过无法用哈希表解决的问题的程序员,但对我们许多人来说,这种习惯很容易养成。正确的方法是停止思考“我如何在Haskell中实现此解决方案”,而是“在Haskell中解决问题的最佳方法是什么”。你可能会惊讶地发现,答案常常不同;我知道我经常是

随机生成器不需要
IO
操作。它们遵循自己的一元法则(有点源于
状态
单子),因此可以通过
随机
单子来表示

在跳过列表的情况下,您可以定义自己的monad,它能够进行概率计算,或者只使用标准的
Random

demo :: Random Int
demo = do
 let l = SkipList.empty

 l2 <- l `add` ("Hello", 42)

 return $ l2 `get` "Hello"
demo::Random Int
演示=做
设l=SkipList.empty

l2跳过列表可以完全实现——只需将当前种子封装在跳过列表本身的状态中

data SkipList a = SkipList StdGen (Node a)
data Node a = ...
这可能会使您面临一些对“真实”跳过列表不实用的复杂攻击,因为您可以探测退化插入顺序并重播对同一种子的攻击,但它可以让您在对抗性使用不是问题时获得该结构的好处

您还可以使用
unsafePerformIO
和一个精心设计的、看似纯粹的界面,却忽略了副作用。虽然不可否认,它的内部并不纯净,但界面却呈现出纯净的外观


也就是说,SkipList的许多经典性能优势来自于它们可以非持久性地实现,这就排除了功能接口。

我曾经尝试过在Haskell中实现跳过列表。当然,这是一个不可变的数据结构(毕竟这是Haskell)。但这意味着对随机性的需求消失了;“fromList”只计算项目数,并为每个项目构建长度合适的跳过数组(每4个项目2个指针,每16个项目3个指针,每64个项目4个指针,等等)


在那一点上,我意识到我只是在构建一个更复杂版本的树,而变异它的能力要小得多。所以我放弃了。

haskell有一个新的基于STM的跳过列表实现,请参阅“关于hackage”。

另外,请继续使用mi