180 likes | 338 Vues
惰性计算. 什么是惰性计算 惰性计算在模块设计中的作用 阅读第十七章. 惰性计算 Lazy Evaluation. Haskell 的主要计算是函数应用: 将函数 f 应用于参数 a1, …, ak 时 , 将它们代入 f 定义等式右边相应的变量 . Haskell 使用惰性计算 : 函数的参数只有在需要计算时才被计算 ; 一个参数不必完全计算,只有需要的部分才被计算; 一个参数最多被计算一次。. 例. 定义函数 g x y = x + 12
E N D
惰性计算 • 什么是惰性计算 • 惰性计算在模块设计中的作用 • 阅读第十七章
惰性计算Lazy Evaluation Haskell的主要计算是函数应用: 将函数f应用于参数a1, …, ak时, 将它们代入f定义等式右边相应的变量. Haskell 使用惰性计算: • 函数的参数只有在需要计算时才被计算; • 一个参数不必完全计算,只有需要的部分才被计算; • 一个参数最多被计算一次。
例 定义函数 g x y = x + 12 则g (9-3) (g (34 3)) (9-3) +12 6+12 18 另一个例子: switch :: Int -> a -> a - >a switch n x y | n >0 = x | otherwise = y (g 34 3) 不需要计算 当 n>0时,不需要计算 y 当 n <= 0时,x不需要计算
例 定义函数 h x = x + x 则 h (9-3) (9-3)+(9-3) 6+6 12 其中(9-3) 只需要计算一次。
计算规则 函数的一般定义 f p1 p2 … pk |g1 = e1 … | otherwise = er where v1 a11 … = r1 f q1 q2 … q k = … 计算 f a1 … ak 包含下列三方面:
计算 – 模式匹配 需要计算输入参数至匹配定义中的某个模式,从而决定使用那个等式。例如, f :: [Int] -> [Int] -> Int f [] xs = 0 f (x:xs) [] = 0 f (x:xs) (y:ys) = x + y 计算f [1..3] [1..3] 过程 f [1..3] [1..3] f (1:[2..]) [1..3] f (1:[2..3]) (1:[2..3]) 1+1
f (2+3) (4-1) (3+9) ?? (2+3)>=(4-1) && (2+3) >=(3+9) … False ?? 3>= 5 && 3 >= 12 False ?? Otherwise True 计算 – 守卫 假设输入匹配第一个等式,下一步是依次计算条件p1 至 pk, 直至一个条件的值是True, 然后使用相应的式子作为输出值 f :: Int -> Int -> Int -> Int f m n p | m >= n && m >= p = m | n >= m && n >= p = n | otherwise = p
计算 – 局部定义 由where 引入的局部定义只有在需要的时候被计算,而且最多计算一次。例如 f :: Int -> Int -> Int f m n | notNil xs = front xs | otherwise = n where xs = [m..n] front (x:y:zs) = x+y front [x] = x f 3 5 7 xs 只需计算一次 f 5 3 3 front 不需计算
计算次序 • 当计算存在次序选择时 • 计算由外至内进行, • 例如 f1 e1 (f2 e2 e3), 先计算f1的函数应用。 • 否则,计算由左至右 • 例如 f1 e1 e2 + f2 e2 e3, 先计算f1 e1 e2, 后计算 f2 e2 e3.
函数是非严格的 • Haskell是一个惰性语言。惰性又称为非严格的 (non-strict) 。 • 一个函数称为严格的,如果当它作用于一个非终止的表达式时,计算也不能终止。 • Haskell 函数是非严格的。这个特性的优点是: • 计算代价大的值可以作为参数传给函数,不需要担心不必要的计算会发生。例如,一个参数可以是无穷的数据结构,我们无需担心内存问题。 • 更重要的是,惰性计算提供了一种模块程序设计方法,它使得一个问题可以用几个模块解决,并用惰性机制将这些模块粘合起来。
无穷数据结构 非严格的构造符函数允许我们定义无穷的数据结构(作为其他函数的输入): ones = 1:ones numsFrom n = n:numsFrom (n+1) squares = map (^2) (numsFrom 0) take 5 squares [0, 1,4,9,16] fib = 1:1:[x+y |(x,y) <- zip fib (tail fib)] 一个1的无穷列表 由n开始的无穷自然数列表 Fibonacci 序列
生成素数( prime numbers ) 埃拉托斯特尼筛法(The Sieve of Eratosthenes)是一种求素数的算法: 删除2之后的所有2的倍数,则第一个未被删除的数3是素数: 2,3,5,7,9, … 然后删除3 (a prime)之后所有3 的倍数,第一个未被删除的数5是素数 : 2, 3, 5, 7, 11, 13, 15, … 然后删除5之后所有5的倍数 … 定义 Haskell 函数 primes :: [Int] primes = sieve [2..]
sieve (x:xs) = x: sieve [y | y <- xs, y ‘mod’ x >0] primes = [2,3,5,7,11, …] 判定一个数是否素数: member :: Ord a => [a] -> a -> Bool member (x:xs) n | x < n = member xs n | x == n = True | otherwise = False member primes 6 False 输入是无穷列表
粘合程序 考虑程序 (g . f), 当它作用与输入 input时 g (f input) 函数 f 可能输出一个很大的输出值,将其存储是不现实的。 在 Haskell 中, f 和 g 的计算是严格同步的:只有g 需要更多的输入时,f 才会生成更多的输出;f 只输出足够 g 消耗的数据。 为此,一个程序可以分解成一个生成大量解的生成器和从中选择一个合适解的选择器,然后利用 惰性机制粘合成解决原问题的解。这种生成器和选择器的分离允许对于每个部分编程或者修改,而结果不会影响另一部分。
Newton-Raphson 求平方根公式 下面的Newton-Raphson公式可以求一个数r的平方根逼近值,初始值是 a0: a(n+1) = (a(n) + r/a(n))/2 对于给定的误差 eps, 当相邻两个逼近值的差小于eps时,逼近可停止。 可以将逼近值表示成一个列表,每一个逼近值都可以由下列函数应用于前一个逼近值生成, next N x = (x +r/x)/2 用f 记 (next r), 则迭代序列是 [a0, f a0, f(f a0), f (f (f a0)), …]
平方根 可以定义函数 repeat f a = a: repeat f (f a) 由此可以计算出迭代序列 repeat (next r) a0 这个列表是潜在无穷的:如果需要,它可以产生任何数目的逼近值。 求平方根的下一个任务是在逼近序列中选出给定误差的逼近值。 可以定义函数within: within eps (a : (b : rest)) | abs (a-b) <= eps = b | otherwise = within eps (b : rest)
粘合 Sqrt a0 eps r = within eps (repeat (next r) a0)
小结 • Haskell是一个非严格、惰性语言 • 函数是非严格的,因此允许惰性计算 • 惰性计算含义 • 函数的参数只有在需要计算时才被计算; • 一个参数不必完全计算,只有需要的部分才被计算; • 一个参数最多被计算一次 • 惰性计算的意义:允许无穷数据结构,利用惰性计算将程序粘合,提供模块化机制。