Haskell怎么有后期评价?
How does Haskell have late evaluation?
听说Haskell评价晚了。然而,无论我尝试做什么,它似乎都以与任何其他编程语言相同的方式对其进行评估;
考虑以下代码:
test :: Bool -> IO()
test n = do
let y = 5
print n
main = do
let y = 8
test (y == 8)
此代码输出:
True
- 为什么此代码会提前评估 "y"?
- 我可以在 Haskell 中有一个延迟评估的例子吗?
首先要记住一些重要的一般信息:
Haskell 变量是 不可变的。
总是,无一例外,你无能为力。
因此,当您编写类似 let y = 5
的内容时,它 永远不会 更改某些已经存在的变量的值。相反,它引入了一个名为 y
的新变量并为其赋予所需的值。在你的程序的其他地方是否还有一个你碰巧称为 y
的变量是无关紧要的,这完全是一个 different 变量。事实上,考虑一下:
main :: IO ()
main = do
let y = 1
do let y = 2
print y
print y
输出为
2
1
在您的示例中,let y = 5
语句根本没有效果,很可能会被编译器完全丢弃。事实上,如果你用 -Wall
编译(你应该这样做),GHC 会告诉你:
/tmp/wtmpf-file30239.hs:4:7: warning: [-Wunused-local-binds]
Defined but not used: ‘y’
|
4 | let y = 5
| ^
所以特别是,y == 8
检查不可能受到您可以使用的任何 let y =
语句的影响。
事实上,更一般地说,惰性求值不会影响值。这是拥有纯函数式语言的一大优点:因为一切都是不变的,惰性求值通常不会影响值语义,它只会影响完成相同工作的顺序——可以 影响某事终止的速度(或它是否完全终止),但不影响它在完成时产生的价值。在运行时进入 test
函数之前或之后评估 y == 8
并不重要,事实上 Haskell 标准并没有说明任何事情——它只说明了如果 test
最终 甚至不使用 参数值,则非终止参数不应阻止 test
终止(非严格语义).因此,以下演示了惰性求值的实际效果:
unobtanium :: Bool
unobtanium = unobtanium -- infinite loop
don'tTest :: Bool -> IO ()
don'tTest a = do
putStrLn "Meh, I'm too lazy to do it."
main :: IO ()
main = do
let y = unobtanium
don'tTest y
...即使无法评估 y
。
Why is this code doing early evaluation?
正如@leftaroundabout 所讨论的那样,并非如此。这里的误解是关于变量范围而不是评估。
can I have an example of late evaluation in Haskell?
无限列表
我发现最早容易理解的惰性计算示例是无限列表。考虑典型的斐波那契数列。为了命名起见,一个详细的版本是:
fibs :: [Integer]
fibs = 0 -- First element
: 1 -- Cons with the second element
: zipWith (\first second -> first + second) fibs (drop 1 fibs)
-- Cons with all other elements, dynamically computed.
有些人不喜欢计算。这些人的一个例子是重复列表。考虑一个永无止境的 42 链表......这在 C 中很难以有用的方式做到。
infiniteFortyTwos = 42 :infiniteFortyTwos
错误
错误可能是最常见的惰性求值,但大多数人并没有真正意识到惰性求值的发生。与布尔运算一起使用时通常称为短路,请考虑 shell 脚本:
var=$(ls /dir/that/does/not/exist 2>/dev/null || echo DNE)
这个习语在编程和开发中随处可见。 Perl 早年经常使用|| die "error"
。在 Haskell 中,类型不鼓励以这种特定方式混合评估和失败,但惰性功能是相同的。
考虑一下这个构思不佳的例程:
headEquals :: Eq a => a -> [a] -> Bool
headEquals v xs = not (null xs) && v == head xs
或常见的 java 和 C 模式:
if(ptr != NULL && *ptr == value) {
...
}
喜结连理
一种更高级的懒惰评估技术是 tying the knot。例如,假设我们要用列表中的最小元素替换列表中的所有元素。让我们假设我们在计算答案时有答案,然后使用返回值作为所述答案:
minList :: [Int] -> [Int]
minList [] = []
minList (m:xs) =
let (theMin, newList) = go theMin m xs
in theMin : newList
where
go realAnswer partialAnswer [] = (partialAnswer, [])
go realAnswer partialAnswer (y:ys) =
let newPartialAnswer = min y partialAnswer
in (realAnswer :) <$> go realAnswer newPartialAnswer ys
听说Haskell评价晚了。然而,无论我尝试做什么,它似乎都以与任何其他编程语言相同的方式对其进行评估;
考虑以下代码:
test :: Bool -> IO()
test n = do
let y = 5
print n
main = do
let y = 8
test (y == 8)
此代码输出:
True
- 为什么此代码会提前评估 "y"?
- 我可以在 Haskell 中有一个延迟评估的例子吗?
首先要记住一些重要的一般信息:
Haskell 变量是 不可变的。
总是,无一例外,你无能为力。
因此,当您编写类似 let y = 5
的内容时,它 永远不会 更改某些已经存在的变量的值。相反,它引入了一个名为 y
的新变量并为其赋予所需的值。在你的程序的其他地方是否还有一个你碰巧称为 y
的变量是无关紧要的,这完全是一个 different 变量。事实上,考虑一下:
main :: IO ()
main = do
let y = 1
do let y = 2
print y
print y
输出为
2
1
在您的示例中,let y = 5
语句根本没有效果,很可能会被编译器完全丢弃。事实上,如果你用 -Wall
编译(你应该这样做),GHC 会告诉你:
/tmp/wtmpf-file30239.hs:4:7: warning: [-Wunused-local-binds]
Defined but not used: ‘y’
|
4 | let y = 5
| ^
所以特别是,y == 8
检查不可能受到您可以使用的任何 let y =
语句的影响。
事实上,更一般地说,惰性求值不会影响值。这是拥有纯函数式语言的一大优点:因为一切都是不变的,惰性求值通常不会影响值语义,它只会影响完成相同工作的顺序——可以 影响某事终止的速度(或它是否完全终止),但不影响它在完成时产生的价值。在运行时进入 test
函数之前或之后评估 y == 8
并不重要,事实上 Haskell 标准并没有说明任何事情——它只说明了如果 test
最终 甚至不使用 参数值,则非终止参数不应阻止 test
终止(非严格语义).因此,以下演示了惰性求值的实际效果:
unobtanium :: Bool
unobtanium = unobtanium -- infinite loop
don'tTest :: Bool -> IO ()
don'tTest a = do
putStrLn "Meh, I'm too lazy to do it."
main :: IO ()
main = do
let y = unobtanium
don'tTest y
...即使无法评估 y
。
Why is this code doing early evaluation?
正如@leftaroundabout 所讨论的那样,并非如此。这里的误解是关于变量范围而不是评估。
can I have an example of late evaluation in Haskell?
无限列表
我发现最早容易理解的惰性计算示例是无限列表。考虑典型的斐波那契数列。为了命名起见,一个详细的版本是:
fibs :: [Integer]
fibs = 0 -- First element
: 1 -- Cons with the second element
: zipWith (\first second -> first + second) fibs (drop 1 fibs)
-- Cons with all other elements, dynamically computed.
有些人不喜欢计算。这些人的一个例子是重复列表。考虑一个永无止境的 42 链表......这在 C 中很难以有用的方式做到。
infiniteFortyTwos = 42 :infiniteFortyTwos
错误
错误可能是最常见的惰性求值,但大多数人并没有真正意识到惰性求值的发生。与布尔运算一起使用时通常称为短路,请考虑 shell 脚本:
var=$(ls /dir/that/does/not/exist 2>/dev/null || echo DNE)
这个习语在编程和开发中随处可见。 Perl 早年经常使用|| die "error"
。在 Haskell 中,类型不鼓励以这种特定方式混合评估和失败,但惰性功能是相同的。
考虑一下这个构思不佳的例程:
headEquals :: Eq a => a -> [a] -> Bool
headEquals v xs = not (null xs) && v == head xs
或常见的 java 和 C 模式:
if(ptr != NULL && *ptr == value) {
...
}
喜结连理
一种更高级的懒惰评估技术是 tying the knot。例如,假设我们要用列表中的最小元素替换列表中的所有元素。让我们假设我们在计算答案时有答案,然后使用返回值作为所述答案:
minList :: [Int] -> [Int]
minList [] = []
minList (m:xs) =
let (theMin, newList) = go theMin m xs
in theMin : newList
where
go realAnswer partialAnswer [] = (partialAnswer, [])
go realAnswer partialAnswer (y:ys) =
let newPartialAnswer = min y partialAnswer
in (realAnswer :) <$> go realAnswer newPartialAnswer ys