【F#2.0系列】定义递归函数
定义递归函数
简单的说,就是使用rec前缀:
> let rec factorial n = if n <= 1 then 1 else n * factorial (n-1);;
val factorial : int -> int
> factorial 5;;
val it : int = 120
众所周知,上例是一个阶乘函数。使用rec前缀使得其可以使用其定义本身。基于区分递归函数与非递归函数的考虑,函数本身默认不可递归调用,这会帮助你控制算法逻辑和增加代码可维护性。
上例可以形象的表示为:
factorial 5
= 5 * factorial 4
= 5 * (4 * factorial 3)
= 5 * (4 * (3 * factorial 2))
= 5 * (4 * (3 * (2 * factorial 1)))
= 5 * (4 * (3 * (2 * 1)))
= 5 * (4 * (3 * 2))
= 5 * (4 * 6)
= 5 * 24
= 120
很多方法都可以使用递归调用的方式编写。例如List.length:
let rec length l =
match l with
| [] -> 0
| h :: t -> 1 + length t
有时递归也会在流程控制上使用,例如下述代码会持续的获取HTML代码,并且输出到屏幕上:
let rec repeatFetch url n =
if n > 0 then
let html = http url
printfn "fetched <<< %s >>> on iteration %d" html n
repeatFetch url (n-1)
递归很强大,但不是一个理想的方式。例如,我们可以使用loop来代替递归,并且可以尽量使用List.map和Array.map来替代loop。
使用递归也要注意结束条件。
我们可以使用and连续定义多个递归函数,这称为互助递归函数(mutually
recursive functions)。例如:
let rec even n = (n = 0u) || odd(n-1u)
and odd n = (n <> 0u) && even(n-1u)
会得到一下的类型:
val even : uint32 -> bool
val odd : uint32 -> bool
当然,更有效率的实现方式如下:
let even (n:uint32) = (n % 2u) = 0u
let odd (n:uint32) = (n % 2u) = 1u
有时候你必须确保递归函数为尾递归(tail
recursive),否则在处理大数据的时候会超出堆栈的处理能力。
函数类型的值(Function
Values)
看一个示例:
> let sites = [ "http://www.live.com";
"http://www.google.com" ];;
val sites : string list
> let fetch url = (url, http url);;
val fetch : string -> string * string
> List.map fetch sites;;
val it : (string * string) list
= [ ("http://www.live.com", "<html>...</html>");
("http://www.google.com", "<html>...</html>"); ]
程序简明易懂,就是获取了list中所有网址的html,并且输出成元组(tuple)的形式。
使用匿名函数类型的值(anonymous
function value)
简单的说,就是使用fun关键字:
let resultsOfFetch = List.map (fun url -> (url, http url)) sites
如有多个参数:
> List.map (fun (_,p) -> String.length p) resultsOfFetch;;
val it : int list = [3932; 2827 ]
值得注意的是,上例中的匿名函数接受一个元组(tuple)的值,并且只关心第二个参数p。使用’_’来表示不关心第一个参数的值。
使用聚集操作符(Aggregate
Operators)计算
例如List.map就被称为一个聚集操作符。看一个例子:
let delimiters = [| ' '; '\n'; '\t'; '<'; '>'; '=' |]
let getWords (s: string) = s.Split delimiters
let getStats site =
let url = "http://" + site
let html = http url
let hwords = html |> getWords
let hrefs = html |> getWords |> Array.filter (fun s -> s = "href")
(site,html.Length, hwords.Length, hrefs.Length)
使用:
> let sites = [ "www.live.com";"www.google.com";"search.yahoo.com" ];;
val sites : string list
> sites |> List.map getStats;;
val it : (string * int * int * int) list
= [("www.live.com", 7728, 1156, 10);
("www.google.com", 2685, 496, 14);
("search.yahoo.com", 10715, 1771, 38)]
这个方法获得了给定website的HTML的长度,单词数,和链接数。
|>操作符称为管道(pipline)操作。
使用|>管道
简单的例子:
let (|>) x f = f x
下面的例子:
[1;2;3] |> List.map (fun x -> x * x * x)
与
List.map (fun x -> x * x * x) [1;2;3]
意义相同。
在某种意义上来说,|>是一个程序反转(function
application in reverse)。不管怎样,使用|>有一些显著的优点:
·
程序更清晰:当与List.map联合使用的时候,|>操作符允许你以一个前置链接(forward-chaining),管道风格(piplined
style)的方式执行一个数据转换。
·
类型推导:使用|>操作符更便于进行类型推导。因为程序是按照从左至右的方式进行类型推导的。
·
完整的定义:
val (|>) : 'T -> ('T -> 'U) -> 'U
使用>>操作符组合函数
首先看一个例子:
let google = http "http://www.google.com"
google |> getWords |> List.filter (fun s -> s = "href") |> List.length
使用>>做同样的事:
let countLinks = getWords >> List.filter (fun s -> s = "href") >> List.length
google |> countLinks
关于>>的完整定义:
let (>>) f g x = g(f(x))
val (>>) : ('T -> 'U) -> ('U -> 'c) -> ('T -> 'c)
个人感觉区别就是>>可以定义一个组合函数。而|>是直接计算。
简单的说,就是使用rec前缀:
val it : int = 120
上例可以形象的表示为:
= 120
很多方法都可以使用递归调用的方式编写。例如List.length:
| h :: t -> 1 + length t
有时递归也会在流程控制上使用,例如下述代码会持续的获取HTML代码,并且输出到屏幕上:
repeatFetch url (n-1)
我们可以使用and连续定义多个递归函数,这称为互助递归函数(mutually recursive functions)。例如:
and odd n = (n <> 0u) && even(n-1u)
会得到一下的类型:
val odd : uint32 -> bool
当然,更有效率的实现方式如下:
let odd (n:uint32) = (n % 2u) = 1u
看一个示例:
("http://www.google.com", "<html>...</html>"); ]
let resultsOfFetch = List.map (fun url -> (url, http url)) sites
如有多个参数:
val it : int list = [3932; 2827 ]
例如List.map就被称为一个聚集操作符。看一个例子:
(site,html.Length, hwords.Length, hrefs.Length)
使用:
("search.yahoo.com", 10715, 1771, 38)]
首先看一个例子:
google |> getWords |> List.filter (fun s -> s = "href") |> List.length
使用>>做同样的事:
google |> countLinks