尾调用与尾递归

Download Report

Transcript 尾调用与尾递归

深入浅出Haskell
第2讲
递归地对List求和
naiveSumList [] = 0
naiveSumList x:xs =
x + naiveSumList xs
尾调用与尾递归
一个函数(调用者,caller)调用
了另外一个函数(被调用者,
callee),而且callee产生的返回
值被caller立即返回出去,这种
形式的调用被称为尾调用(tail
call)。如果caller和callee是同
一个函数,那么便将这个尾调
用称为尾递归(tail recursion)。
尾调用优化
从实现的角度看,尾调用并不需要为callee开辟一个新的栈帧
(stack frame),它需要的参数和局部数据可以复用caller的栈帧
空间,然后通过一个jump执行callee的代码,从而避免付出通常
函数调用的代价。这种优化代码的方法被称为尾调用消除(tail call
elimination)或者尾调用优化(tail call optimization)。
对List求和:尾递归版本
sumList ls = sumLoop ls 0
where
sumLoop [] a = a
sumLoop (x:xs) a =
sumLoop xs (a + x)
你的语言支持尾调用优化吗?
//JavaScript
function foo(counter,accum)
{
if (counter <= 0)
{
return accum;
}
return
foo(counter - 1,
accum + counter);
}
alert(foo(3000,0));
//Lua
function foo(counter, accum)
if counter <= 0 then
return accum
end
return
foo(counter - 1,
accum + counter)
end
print(foo(300000,0))
尾递归与循环
sumList ls = sumLoop ls 0
where
隐式的累积变量
sumLoop [] a = a
sumLoop (x:xs) a =
sumLoop xs (a + x)
显式传递的累积变量
//某种命令式语言
int a = 0; int i = 0;
for(i=0;i<list.len;++i)
{
a = a + list[i];
}
中缀函数
data a `MyPair` b = a `MyPair` b
deriving Show
-- 解释器加上-XTypeOperators选项
-- v1 :: Int `MyPair` String
v1 :: MyPair Int String
v1 = 10 `MyPair` "hello"
Side Effects - 副作用
• 目前为止,我们遇到的所有程序都是纯粹的,它们的执行仅仅是为了
获取表达式的值
• 可有的时候我们希望程序能够执行一些影响真实世界的动作
(action),比如打印、控制机器人、绘图等等
• 除了“纯洁的值”Haskell还有一种被称为action的特殊的值,类
型为IO a。一个类型为IO a的表达式告诉系统,它除了会返回a类
型的值以外,也有可能做一些影响外部世界或者受外部世界影响的动
作。
一些预定义的动作
-- get one character from keyboard
getChar :: IO Char
-- write one character to terminal
putChar :: Char -> IO ()
-- get a whole line from keyboard
getLine :: IO String
-- read a file as a String
readFile :: FilePath -> IO String
-- write a String to a file
writeFile :: FilePath -> String -> IO ()
Unit类型
• 有一种类型,写成(),被称为Unit类型
• 它只有一个值,也写成()
• 它经常用在不需要返回有用值的地方
复合动作
var<-action用于将动作相关的返回值
取出,其后所有动作均可使用var变量
do关键字将一
组动作串起来
形成复合动作
do
:: String
:: IO String
:: IO String
s1 <- getLine
s2 <- readFile "test.log"
:: String
writeFile "another.log" (s1 ++ s2)
return True
:: IO Bool
return将一个a类型的值转化为IO a类型的动作,
但该动作什么也不干,仅仅是将值返回。最后一个
动作的类型也决定了整个do表达式的类型
:: IO ()
不是“v <- …”这种形式
的action通常都是这种类
型
递归动作
getLine :: IO String
getLine =
do c <- getChar
-- get a character
if c == '\n'
-- if it’s a newline
then return ""
-- then return empty string
-- 递归地获取一行剩下的部分,最后返回整行数据
else do l <- getLine
return (c:l)
动作列表
actionList =
[putStr "Hello World\n",
writeFile "test.txt" "Hello File",
putStr "File successfully written"]
-- sequence_ :: [IO a] -> IO ()
main = sequence_ actionList
动作什么时候执行?
• 类型为IO a的动作仍然是一个值,它只有在被执行的时
候才能产生side effect。如果该动作被别的动作调用,
那么只有调用它的动作被执行的时候才有机会跟着被执行。
• 一个Haskell程序的值是该程序Main模块中的main变量
对应的值,如果它是IO a类型的action,那么就会被执
行;如果不是,值就会直接打印出来。
• 在解释器中,如果输入的表达式是IO a类型的action,
那么相应的动作就会被执行,否则就仅仅打印求值结果。
图形编程
• 传统的图形编程是基于动作的
• 最基本的动作有打开、关闭图形窗口
• 另外一些则是在图形窗口中绘制线条、圆、文
字等等
若干图形操作
-- 新建一个指定标题指定大小的图形窗口
openWindow :: String -> Size -> IO Window
type Size = (Int, Int)
-- 在指定窗口中把图元给画出来
drawInWindow :: Window -> Graphic -> IO ()
-- 等着用户按下键,并将按键信息返回
getKey :: Window -> IO Char
-- 关闭窗口
closeWindow :: Window -> IO ()
基本图元与颜色
type Point = (Int, Int)
text :: Point -> String -> Graphic
ellipse :: Point -> Point -> Graphic
shearEllipse :: Point -> Point -> Point -> Graphic
line :: Point -> Point -> Graphic
polygon :: [Point] -> Graphic
polyline :: [Point] -> Graphic
-- 将一种图元转化为具有指定颜色的图元
withColor :: Color -> Graphic -> Graphic
data Color =
Black | Blue | Green | Cyan |
Red | Magenta | Yellow | White
Graphical "Hello World"
import SOE
main0 =
runGraphics $
do w <- openWindow "First window" (300,300)
drawInWindow w (text (100,200) "hello world")
k <- getKey w
closeWindow w
响应用户的特定输入
spaceClose :: Window -> IO ()
spaceClose w =
do k <- getKey w
if k == ' ' then closeWindow w
else spaceClose w
-- ($) :: (a -> b) -> a -> b
-- infixr 0 $
main1 =
runGraphics $
do w <- openWindow "Second Program" (300,300)
drawInWindow w (text (100,200) "Hello Again")
spaceClose w
坐标系统
Increasing x-axis
(0,0)
Increasing y-axis
画点儿什么吧
main2 =
runGraphics $
do w <- openWindow "Draw some shapes" (300,300)
drawInWindow w (ellipse (0,0) (50,50))
drawInWindow w
(shearEllipse (0,60) (100,120) (150,200))
drawInWindow w
(withColor Red (line (200,200) (299,275)))
drawInWindow w
(polygon [(100,100),(150,100),(160,200)])
drawInWindow w
(withColor Green
(polyline [(100,200),(150,200),
(160,299),(100,200)]))
spaceClose w
结果
复杂一点的例子
Sierpinski’s
Triangle,一种分形
图形,不断地在更小
的尺度上重绘自己,
典型的递归。
三角形顶点的确定
(x,y-size)
size2 + size2 = hyp2
注意Y轴是向下增长的
hyp
(x,y-size/2)
(x,y)
(x+size/2,y-size/2)
(x+size/2,y)
(x+size,y)
一个三角形的绘制
fillTri x y size w =
drawInWindow w
(withColor Blue
(polygon [(x,y),
(x+size,y),
(x,y-size)]))
size
(x,y)
size
Sierpinski’s Triangle
minSize = 8
sierpinskiTri w x y size =
(x,y-size/2)
if size <= minSize
then fillTri x y size w
(x,y)
else let size2 = size `div` 2
(x+size/2,y)
(x+size,y)
in do sierpinskiTri w x y size2
sierpinskiTri w x (y-size2) size2
sierpinskiTri w (x+size2) y size2
main3 =
runGraphics $
do w <- openWindow "Sierpinski's Tri" (400,400)
sierpinskiTri w 50 300 256
spaceClose w
小结
• 尾调用、尾递归、循环
• 函数的中缀写法
• IO a:带状态的动作
• do的语法
• 复合动作、递归动作
• 图形编程
• 分形与递归
有没有问题?
关于作者
@邓际锋, 网易杭研院程序员
个人研究兴趣
 编程语言的设计与实现
 交互式艺术及FP在该领域的应用
 如何为儿童和非技术人员开发富有乐趣的创作工具
http://weibo.com/ideamonad
[email protected]
http://blog.csdn.net/soloist