Haskell/Frege 会重新计算惰性列表的元素吗?
Does Haskell/Frege ever recalcuate elements of a lazy list?
假设我有一个所有素数的列表,定义为
primes :: (Enum α, Integral α) => [α]
primes = sieve [2..]
where sieve :: (Integral α) => [α] -> [α]
sieve [] = undefined
sieve (x:xs) = x : (sieve $ filter ((/= 0) . (flip mod x)) xs)
我想通过多个不同的函数来喂养 primes
,例如:
sumOfPrimesLessThan :: (Integral α) => α -> α
sumOfPrimesLessThan n = sum $ takeWhile (< n) primes
或
productOfPrimesLessThan :: (Integral α) => α -> α
productOfPrimesLessThan n = foldl (*) 1 $ takeWhile (< n) primes
什么的,就好像在做
main = do print (sumOfPrimesLessThan 1000 :: Integer)
print (productOfPrimesLessThan 1000 :: Integer)
Haskell 是否会在任何时间点重新计算 primes
或其任何部分?缓存什么?什么不会被缓存?
附录 0:假设我有另一个函数
prime = flip elem primes
如果 prime
使用不同的参数进行评估,每个评估是否会重新评估 primes
?例如:
allPrime = all prime
在你的情况下(对于 Haskell GHC)答案是 primes
被重新计算。然而,如果你有一个像 primes :: [Int]
这样的单态签名,那就不是这样了。这是调试它的方法:导入 Debug.Trace
并将 trace
函数添加到 primes
primes :: (Enum α, Integral α) => [α]
primes = trace "call to primes" $ sieve [2..]
where sieve :: (Integral α) => [α] -> [α]
sieve [] = undefined
sieve (x:xs) = x : (sieve $ filter ((/= 0) . (flip mod x)) xs)
现在,每次调用 primes
时,您都会看到 "call to primes" 被打印出来。编译您的程序(有或没有优化),我收到两次 primes
.
的调用
但是为什么呢?
Haskell 实际上将您的 primes
版本编译为一个接受一个类型参数的函数,因此在 sumOfPrimesLessThan
和 [=22= 中使用 primes
] 实际上是一个函数调用(出于非常明显的原因,函数调用通常不会存储在 Haskell 中)。例如,当您调用 sumOfPrimesLessThan 1000 :: Integer
时,您实际上给了它两个参数:值 1000
和类型参数 Integer
。 sumOfPrimesLessThan
然后将第二个参数传递给素数。
另一方面,如果您的类型签名是单态的,例如 primes :: [Int]
、sumOfPrimesLessThan :: Int -> Int
和 productOfPrimesLessThan :: Int -> Int
,Haskell 编译 primes
下降到只是一个常规的 thunk 值,所以它只被评估一次。
是我不久前给出的关于Haskell如何在里面表示值和函数的另一种解释。
SPECIALIZE
pragma(特定于 GHC)
有时您希望能够告诉 GHC,即使您的表达式是多态的,您也希望它被视为几个类型的单态。 (所以你有点希望你有第二个版本 primes :: [Integer]
即使通常 primes :: (Enum α, Integral α) => [α]
。)你可以使用 SPECIALIZE
pragma:
来指定它
{-# SPECIALIZE primes :: [Integer] #-}
primes :: (Enum a,Integral a) => [a]
primes = ...
现在,仅对于 Integer
类型,primes
将只计算一次。对于其他类型(如 Int
),我们仍然会得到与以前相同的行为。
回应附录
对于 prime = flip elem primes
的多次调用,其中 prime
是单态的 ("instantiated"),您仍然只能调用一次 primes
。一旦 primes
是单态的,它就可以随处共享。另外,请注意 allPrime = all prime
示例中的单态限制 - 您需要指定 Foldable
想要的实例(allPrime = all prime
与 allPrime xs = all prime xs
略有不同)。
假设我有一个所有素数的列表,定义为
primes :: (Enum α, Integral α) => [α]
primes = sieve [2..]
where sieve :: (Integral α) => [α] -> [α]
sieve [] = undefined
sieve (x:xs) = x : (sieve $ filter ((/= 0) . (flip mod x)) xs)
我想通过多个不同的函数来喂养 primes
,例如:
sumOfPrimesLessThan :: (Integral α) => α -> α
sumOfPrimesLessThan n = sum $ takeWhile (< n) primes
或
productOfPrimesLessThan :: (Integral α) => α -> α
productOfPrimesLessThan n = foldl (*) 1 $ takeWhile (< n) primes
什么的,就好像在做
main = do print (sumOfPrimesLessThan 1000 :: Integer)
print (productOfPrimesLessThan 1000 :: Integer)
Haskell 是否会在任何时间点重新计算 primes
或其任何部分?缓存什么?什么不会被缓存?
附录 0:假设我有另一个函数
prime = flip elem primes
如果 prime
使用不同的参数进行评估,每个评估是否会重新评估 primes
?例如:
allPrime = all prime
在你的情况下(对于 Haskell GHC)答案是 primes
被重新计算。然而,如果你有一个像 primes :: [Int]
这样的单态签名,那就不是这样了。这是调试它的方法:导入 Debug.Trace
并将 trace
函数添加到 primes
primes :: (Enum α, Integral α) => [α]
primes = trace "call to primes" $ sieve [2..]
where sieve :: (Integral α) => [α] -> [α]
sieve [] = undefined
sieve (x:xs) = x : (sieve $ filter ((/= 0) . (flip mod x)) xs)
现在,每次调用 primes
时,您都会看到 "call to primes" 被打印出来。编译您的程序(有或没有优化),我收到两次 primes
.
但是为什么呢?
Haskell 实际上将您的 primes
版本编译为一个接受一个类型参数的函数,因此在 sumOfPrimesLessThan
和 [=22= 中使用 primes
] 实际上是一个函数调用(出于非常明显的原因,函数调用通常不会存储在 Haskell 中)。例如,当您调用 sumOfPrimesLessThan 1000 :: Integer
时,您实际上给了它两个参数:值 1000
和类型参数 Integer
。 sumOfPrimesLessThan
然后将第二个参数传递给素数。
另一方面,如果您的类型签名是单态的,例如 primes :: [Int]
、sumOfPrimesLessThan :: Int -> Int
和 productOfPrimesLessThan :: Int -> Int
,Haskell 编译 primes
下降到只是一个常规的 thunk 值,所以它只被评估一次。
SPECIALIZE
pragma(特定于 GHC)
有时您希望能够告诉 GHC,即使您的表达式是多态的,您也希望它被视为几个类型的单态。 (所以你有点希望你有第二个版本 primes :: [Integer]
即使通常 primes :: (Enum α, Integral α) => [α]
。)你可以使用 SPECIALIZE
pragma:
{-# SPECIALIZE primes :: [Integer] #-}
primes :: (Enum a,Integral a) => [a]
primes = ...
现在,仅对于 Integer
类型,primes
将只计算一次。对于其他类型(如 Int
),我们仍然会得到与以前相同的行为。
回应附录
对于 prime = flip elem primes
的多次调用,其中 prime
是单态的 ("instantiated"),您仍然只能调用一次 primes
。一旦 primes
是单态的,它就可以随处共享。另外,请注意 allPrime = all prime
示例中的单态限制 - 您需要指定 Foldable
想要的实例(allPrime = all prime
与 allPrime xs = all prime xs
略有不同)。