Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/three.js/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
是哈斯克尔';懒惰是Python的优雅替代品';什么是发电机?_Python_Haskell_Lazy Evaluation - Fatal编程技术网

是哈斯克尔';懒惰是Python的优雅替代品';什么是发电机?

是哈斯克尔';懒惰是Python的优雅替代品';什么是发电机?,python,haskell,lazy-evaluation,Python,Haskell,Lazy Evaluation,在编程练习中,首先要求它编程阶乘函数,然后计算和:1!+2! + 3! +... nin(因此我们不能直接使用阶乘)。我不是在寻找这个具体(琐碎)问题的解决方案,我在尝试探索Haskell的能力,这个问题是我想玩的玩具 我认为Python的生成器可以很好地解决这个问题。例如: from itertools import islice def ifact(): i , f = 1, 1 yield 1 while True: f *= i i

在编程练习中,首先要求它编程阶乘函数,然后计算和:
1!+2! + 3! +... nO(n)
乘法中的code>in(因此我们不能直接使用阶乘)。我不是在寻找这个具体(琐碎)问题的解决方案,我在尝试探索Haskell的能力,这个问题是我想玩的玩具

我认为Python的生成器可以很好地解决这个问题。例如:

from itertools import islice
def ifact():
    i , f = 1, 1
    yield 1
    while True:
        f *= i
        i += 1
        yield f

def sum_fact(n):
    return sum(islice(ifact(),5))
然后我试图找出Haskell是否有类似的行为,而不是这个生成器,我认为懒惰会影响所有员工,而没有任何额外的概念

例如,我们可以用

fact = scan1 (*) [1..]
然后用以下方法解决练习:

sum n = foldl1 (+) (take n fact)
我想知道这个解决方案在时间复杂度和内存使用方面是否真的与Python的解决方案“等效”。我想说,Haskell的解决方案从不存储所有列表事实,因为它们的元素只使用一次

我是对的还是完全错了


编辑: 我应该更准确地检查:

Prelude> foldl1 (+) (take 4 fact)
33
Prelude> :sprint fact
fact = 1 : 2 : 6 : 24 : _

因此(我的实现)Haskell存储结果,即使它不再使用。

基本上,是的:Haskell的惰性列表非常类似于Python的生成器,如果这些生成器可以轻松地克隆、缓存和组合的话。您可以从递归函数返回
[]
,它可以将状态线程化到生成器中,而不是引发
StopIteration

由于自递归,它们做了一些更酷的事情。例如,您的阶乘生成器更惯用地生成,如:

facts = 1 : zipWith (*) facts [1..]
或者Fibonaccis作为:

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
通常,任何迭代循环都可以通过将循环状态提升为函数的参数,然后递归调用它来获得下一个循环周期,从而转换为递归算法。生成器就是这样的,但是我们在递归函数的每次迭代中都会预先准备一些元素,`go ___=(stuff):go ___;

因此,完美的等价物是:

ifact :: [Integer]
ifact = go 1 1
  where go f i = f : go (f * i) (i + 1)

sum_fact n = sum (take n ifact)
就什么是最快而言,Haskell中绝对最快的可能是“for循环”:

sum\u事实n=go 1
我去哪里

|我你的例子在内存使用方面不相等。很容易看出您是否将
*
替换为
+
(这样数字就不会太快变大),然后在大型
n
上运行这两个示例,例如10^7。您的Haskell版本将消耗大量内存,python将保持较低的内存

Python生成器不会生成一个值列表,然后对其求和。相反,
sum
函数将从生成器中逐个获取值并进行累加。因此,内存使用将保持不变

Haskell将惰性地计算函数,但为了计算say
foldl1(+)(以事实为例)
它必须计算完整的表达式。对于大型
n
而言,这将以与
(foldl(+)0[0..n])
相同的方式展开为一个大型表达式。有关评估和缩减的更多详细信息,请参见此处:

您可以使用
foldl1'
而不是上面链接中描述的
foldl1
来修复您的
sum n
。正如@user2407038在他的评论中解释的那样,您还需要保持
fact
local。以下内容在GHC中使用恒定内存:

let notfact=scanl1(+)[1..]
设n=20000000
设res=foldl'(+)0(取n个notfact)

请注意,在实际阶乘代替
notfact
的情况下,内存方面的考虑就不那么重要了。数字会很快变大,任意精度的算术运算会减慢速度,因此您无法获得
n
的大值以实际看到差异。

确实,惰性列表可以这样使用。但也有一些细微的区别:

  • 列表是数据结构。因此,您可以在评估它们之后保留它们,这可能是好的,也可能是坏的(您可以避免重新计算值和使用@ChrisDrost所描述的递归技巧,但代价是保持内存未释放)
  • 清单是纯粹的。在生成器中,您可以进行带有副作用的计算,但不能使用列表(这通常是可取的)
  • 由于Haskell是一种懒惰的语言,懒惰无处不在,如果您只是将一个程序从命令式语言转换为Haskell,内存需求可能会发生很大的变化(正如@RomanL在他的回答中所描述的)
但是Haskell提供了更高级的工具来完成生成器/使用者模式。目前有三个库关注此问题:。我最喜欢的是,它易于使用,并且其类型的复杂性保持较低

它们有几个优点,特别是您可以创建复杂的管道,并且可以基于选定的monad创建管道,这允许您说明管道中允许哪些副作用

使用导管,您的示例可以表示为:

import Data.Functor.Identity
导入数据。管道
将符合条件的Data.conductor.List作为C导入
IFATCC::(数字a,单子m)=>生产者m a
IFATCC=回路1
哪里
循环rn=设r'=r*n
在产量r'>>回路r'(n+1)中
sumC::(Num a,Monad m)=>消费者a m a
sumC=C.fold(+)0
main::IO()
main=(print.runIdentity)(IFATCC$=C.5$$sumC)
--或者直接在IO monad中运行管道:
--主=(IFATCC$=C.5$$sumC)>>=打印

在这里,我们创建一个无限期产生阶乘的
生产者
(一个不消耗任何输入的管道)。然后我们用
隔离
组合它,它确保通过它传播的值不超过给定数量,然后我们用一个
消费者
组合它,它只对值求和并返回结果。

有一些相似之处,但也有一些不同之处:Python ge
sum_fact n = go 1 1 1
  where go acc fact i
          | i <= n = go (acc + fact) (fact * i) (i + 1)
          | otherwise = acc