我非常喜欢Haskell,但空间泄漏对我来说有点担心。我通常认为Haskell的类型系统比C ++更安全,但是使用C风格的循环我可以相当肯定它会在不耗尽内存的情况下完成,而Haskell“折叠”可能会耗尽内存,除非你小心适当的字段是严格的。
我想知道是否有一个使用Haskell类型系统的库,以确保可以编译各种构造并以不构建thunk的方式运行。例如,no_thunk_fold
如果以可能构建thunk的方式使用它,则会抛出编译器错误。我明白这可能会限制我能做什么,但是我想要一些我可以使用的功能作为一个选项,这会让我更自信我没有意外地在某个地方留下一个重要的非严格字段而且我将要用尽太空了。
答案 0 :(得分:10)
听起来你担心懒惰评价的一些不利方面。您希望确保在常量内存中处理折叠,循环,递归。
创建了iteratee库解决了这个问题, pipes, conduit, enumerator, iteratee, iterIO
最受欢迎,也是最近的 pipes和 conduit。这两者都超越了iteratee模型。
在 pipes库专注于在理论上是合理的,以消除错误,并允许设计的稳定性打开高效但高水平的抽象(我的话不是作者)。如果需要,它还提供双向流,这是迄今为止图书馆独有的优势。
在 conduit在理论上并不像管道那么有根据,但它具有很大的好处,目前在它上面构建了更多的关联库来解析和处理http流,xml流等等。查看包页面上hackage处的导管部分。它被使用了 yesod Haskell的一个较大且众所周知的Web框架。
我很喜欢用管道库编写流媒体应用程序,特别是能够制作代理变换器堆栈。当我需要获取网页或解析一些xml时,我一直在使用管道库。
我还应该提一下 io-streams刚刚完成first official release。它的目标特别是在IO,它的名字并不奇怪,并且使用更简单的类型机器,更少的类型参数,然后 pipes或 conduit。主要的缺点是你被困在IO monad中,因此它对纯代码没有多大帮助。
{-# language NoMonoMorphismRestriction #-}
import Control.Proxy
从简单翻译开始。
map (+1) [1..10]
变为:
runProxy $ mapD (+1) <-< fromListS [1..10]
对于简单的翻译,iteratee喜欢的产品更加冗长,但是通过更大的例子提供大量的胜利。
代理管道库的一个示例,它在常量sapce中生成斐波纳契数
fibsP = runIdentityK $ (\a -> do respond 1
respond 1
go 1 1)
where
go fm2 fm1 = do -- fm2, fm1 represents fib(n-2) and fib(n-1)
let fn = fm2 + fm1
respond fn -- sends fn downstream
go fm1 fn
这些可以用stdout流式传输到stdout runProxy $ fibsP&gt; - &gt; printD - printD只打印下游值,Proxies是管道包的双向提供。
你应该查看我刚刚发现的proxy tutorial和conduit tutorial现在在FP Complete的Haskell学校。
找到平均值的一种方法是:
> ((_,l),s) <- (`runStateT` 0) $ (`runStateT` 0) $ runProxy $ foldlD' ( flip $ const (+1)) <-< raiseK (foldlD' (+)) <-< fromListS [1..10::Int]
> let m = (fromIntegral . getSum) s / (fromIntegral . getSum) l
5.5
现在可以轻松添加地图或过滤代理。
> ((_,l),s) <- (`runStateT` 0) $ (`runStateT` 0) $ runProxy $ foldlD' ( flip $ const (+1)) <-< raiseK (foldlD' (+)) <-< filterD even <-< fromListS [1..10::Int]
编辑:重写代码以利用状态monad。
更新
关于以可罗经的方式对大量数据进行多次计算的更多方法,然后在博客文章beautiful folding中演示了写直接递归。折叠变成数据并在使用严格累加器时组合。我没有经常使用这种方法,但它确实似乎隔离了需要严格的地方,使其更容易应用。您还应该查看使用applicative实现相同方法的answer to another question similar question,根据您的偏好,可能更容易阅读。
答案 1 :(得分:7)
Haskell的类型系统无法做到这一点。我们可以用一个完全多态的术语来证明这一点,以吃任意数量的ram。
takeArbitraryRAM :: Integer -> a -> a
takeArbitraryRAM i a = last $ go i a where
go n x | n < 0 = [x]
go n x | otherwise = x:go (n-1) x
要做你想做的事,需要substructural types。线性逻辑对应于lambda演算的有效可计算片段(您还需要控制递归)。添加结构公理可以使您获得超指数时间。
Haskell允许您伪造线性类型,以便使用索引monad管理某些资源。不幸的是,空间和时间都被语言所覆盖,所以你不能为它们做到这一点。您可以执行注释中建议的操作,并使用Haskell DSL生成具有性能限制的代码,但此DSL中的计算术语可能需要任意长并使用任意空间。
不要担心空间泄漏。抓住他们。轮廓。您的代码证明复杂性限制的原因。无论您使用何种语言,您都必须这样做。