我听说haskell的评估较晚。但是,无论我尝试做什么,似乎都可以像其他编程语言一样对它进行评估。
请考虑以下代码:
test :: Bool -> IO()
test n = do
let y = 5
print n
main = do
let y = 8
test (y == 8)
此代码输出:
True
谢谢。
首先要记住的一些重要的一般信息:
Haskell变量是immutable。总是无一例外,对此您无能为力。
因此,当您编写类似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 =
语句的影响。
实际上,更普遍地说,惰性评估不会影响值。那是拥有一种纯粹的功能性语言的伟大之处之一:因为一切都是不变的,所以惰性求值通常不会影响值语义,它只会影响完成相同工作的顺序–这[y == 8
函数之前或之后评估test
都无关紧要,实际上,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
。
为什么此代码要进行早期评估?
不是,正如@leftaroundabout所讨论的。这里的误解是关于变量作用域而不是评估。
我可以举一个在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) { ... }
更高级的惰性评估技术正在解决这一难题。例如,假设我们要用列表中最小的元素替换列表中的所有元素。让我们假设在计算答案时有答案,然后使用返回值作为答案:打结
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