【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)

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

目录传送门 

转载于:https://www.cnblogs.com/pandora/archive/2010/09/01/FSharp_RecFunction.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值