【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.mapArray.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)]

 

这个方法获得了给定websiteHTML的长度,单词数,和链接数。

|>操作符称为管道(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)

个人感觉区别就是>>可以定义一个组合函数。而|>是直接计算。

目录传送门 

posted on 2010-09-01 17:36  Pandora  阅读(1915)  评论(1编辑  收藏  举报

导航