惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 叶小钗
Microsoft Azure Blog
Microsoft Azure Blog
Stack Overflow Blog
Stack Overflow Blog
Jina AI
Jina AI
Vercel News
Vercel News
H
Help Net Security
Martin Fowler
Martin Fowler
美团技术团队
云风的 BLOG
云风的 BLOG
Y
Y Combinator Blog
阮一峰的网络日志
阮一峰的网络日志
MyScale Blog
MyScale Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
博客园 - 司徒正美
人人都是产品经理
人人都是产品经理
Engineering at Meta
Engineering at Meta
G
Google Developers Blog
Blog — PlanetScale
Blog — PlanetScale
MongoDB | Blog
MongoDB | Blog
宝玉的分享
宝玉的分享
小众软件
小众软件
T
Tailwind CSS Blog
WordPress大学
WordPress大学

博客园 - Pandora

MongoDB on Asp.Net MVC3–CRUD MongoDB on Asp.Net MVC3 [WebGL] 简介,流程及示例 2010 Web前端技术趋势及总结 转载 - 杂念0828 【F#2.0系列】使用选项(Option) 【F#2.0系列】使用F#的List 【F#2.0系列】介绍String类型 - Pandora - 博客园 【F#2.0系列】使用F#进行算术操作 【F#2.0系列】F#调用.NET 类库 【F#2.0系列】目录 【F#2.0系列】F#入门(2) 【F#2.0系列】F#入门(1) 【F#2.0系列】概述 活该如此 Visual Studio 2010 XAML Editor IntelliSense Extension Microsoft 发布了第一个IE9 developer preview 支持HTML5硬件加速 ASP.NET 4.0 来了 []()+! 实现JavaScript代码的原理
【F#2.0系列】定义递归函数
Pandora · 2010-09-01 · via 博客园 - Pandora

定义递归函数

简单的说,就是使用rec前缀:

> let rec factorial n = if n <= 1 then 1 else n * factorial (n-1);;
val factorial : int -> int
 
> factorial 5;;

众所周知,上例是一个阶乘函数。使用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

很多方法都可以使用递归调用的方式编写。例如List.length

let rec length l =
    match l with
    | [] -> 0

有时递归也会在流程控制上使用,例如下述代码会持续的获取HTML代码,并且输出到屏幕上:

let rec repeatFetch url n =
    if n > 0 then
        let html = http url
        printfn "fetched <<< %s >>> on iteration %d" html n

递归很强大,但不是一个理想的方式。例如,我们可以使用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

当然,更有效率的实现方式如下:

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

关于>>的完整定义:

let (>>) f g x = g(f(x))
val (>>) : ('T -> 'U) -> ('U -> 'c) -> ('T -> 'c)

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

目录传送门