结合实例实习F#(三)--理解函数式语言中的函数
前两篇我主要说了些F#中基本的语法,今天我接着来说说函数,函数在函数式编程中起着非常重要的作用,可以夸张一点来说,如果你了解并能熟练应用函数,你就可以向别人说“我精通函数式编程了“。
经常有人觉得F#难懂难用,我觉得一部分原因是F#中的函数接口(这里的接口指的是function signature, 我习惯叫它函数接口,如果对您阅读带来什么不便,请见谅). 看起来和我们平常熟悉的很不一样(比如C#),导致一些朋友在尝鲜阶段遇到困难,进而觉得其难懂难用,最后彻底将F#打入冷宫。希望下面的内容能让大家觉得F#的函数不再难懂
还是老规矩,先来看一个例子
1 let res = Seq.unfold (fun (a,b) ->Some(a,(a+b,a))) (0,1)
2 Seq.iter(fun x -> printf "%d " x) ( Seq.take 10 res)
(这个例子中用到了F#中一个基本的immutable类型Seq<'T>, 你可简单的把它等同于C# 3.0中鼎鼎大名的IEnumerable<T> , 由于这不是本文的重点,所以略去不表)
这段代码是运行结果是
0 1 1 2 3 5 8 13 21 34 (注意第二行的后面,我只取了sequence的前10个元素,Seq.take 10 res).看到输出结果,我想大家都明白了这个程序第一行是在生成Fibonacci数列,让我们来看一下Seq.unfold方法的接口
val unfold: ('State -> ('T * 'State) option) -> 'State -> Seq<'T>
我想很多人在初学F#时看过这样的函数接口说明会感到很陌生吧,没关系,这正是本文的目的,让你对其不再陌生。
F#使用箭头 "->"来标记函数接口,比如 int -> string 就表示这是一个接受一个int型参数并返回一个string值的函数。比如 let f (x:int) = x.ToString()
了解了这个好象并不能帮助我们来理解Seq.unfold方法的接口,要理解它,我们还需要了解一些基本概念。
Higher-Order Function. 有可能以前你没听说过这种提法,但不要被这个陌生的名字吓到,其实我们大家在中学时就接触过这个东西了,不信你看这个
sin(x+y) = sinxcosy+cosxsiny。三角和角公式,这个大家总该有些记忆吧? 有人要问这和Higher-Order Function有什么关系呢?别急,让我们先看看Higher-Order Function的定义。SICP 里是这样定义的:Procedures that manipulate procedures are called higher-order procedures。(定义里的procedures就是我们这里说的Function的意思).定义中提到的操作(manipulate)函数不由让我们想入非非,怎么样操作函数?把函数当作普通的参数来理解不就简单多了,( 简单点就是一个函数接受的参数可以是函数,它的返回值也可以是函数)。那为什么要叫Higher-Order procedures呢?你的老板可以成天让你做这个干那个,你能反过来指挥你老板做事么J 再看下我上面说的三角和角公式,把sin和cos都看成是函数的话,呵呵,是不是我们在高中就接触过Higher-Order Function了?
有了Higher-Order Function的概念,好象还是不能让我们很快看明白Seq.unfold该怎么用。让我们接着来看一个简单的函数
let add x y = x+y ( 我们看到这个函数的接口是val add: int -> int -> int)
让我们接着分析一下这个简单的方法,F#中箭头"->“来表示一个函数,并且它是从右到左结合的,所以我们可以把int->int->int看成int->(int->int),结合刚刚说过的Higher-Order Function,这个就变得很易容易理解了,Add接受一个int型参数,返回一个 int->int的函数。让我们根据这个把add来改写一下使其更直观一些
let add x y = (fun x -> (fun y -> x+y) ) (这个很容易读懂了吧? 接受一个参数x,返回一个函数 fun y -> x+y,返回的函数接受一个参数y,并且返回x+y的值。)
有了上面的基础,让我们更进一步,
let add10 = add 10
我想这个大家应该都能看明白了吧, let add10 = (fun x -> (fun y -> x+y)) 10 = fun y -> 10+y。类似于add10这种用法,在F#中叫做Currying Fuction, 这里的curry跟咖哩没任何关系,它和Haskell语言的命名一样,都是为了纪念著名逻辑学专家Haskell Curry,当然currying function也不是F#独用的,实际上你几乎可以在任何函数式语言上看到它的身影。
讲了这么多了,让我们回到最开始的例子,
val unfold: ('State -> ('T * 'State) option) -> 'State -> Seq<'T>
现在这个看起来没前面那么难了吧, unfold方法接受一个('State -> ('T * 'State) option)的参数,返回一个接受'State并返回Seq<'T>的函数。接受的('State->('T *'State) option)的参数又是什么? 当然是一个接受'State参数并返回 ('T * 'State) option的函数。接着我们采用笨办法来理解let res = Seq.unfold (fun (a,b) ->Some(a,(a+b,a))) (0,1)一一对应之,(a,b)对应于'State, Tuple(a,(a+b,a))中逗号前的a对应于'T,后面的(a+b,a)对应于 'T*'State,因为我们看到接口说明中是('T * 'State) option,所以我们相应的加上Some关键字(有关option type,参见上一篇),后半部分我就不多解释了,通过一一对应,我们看到Seq里存的值是Tuple(a,(a+b,a))中的第一项,也就是逗号前的a了。这下你明白怎么读懂一个函数接口了吧?
如果你刚刚开始学F#,请做几个练习巩固一下今天学的知识吧?你能根据下面所写的函数接口构造一个函数么?
1. 'a -> ('a->'b)->'b
2. ('a->'b) ->('c -> 'a) -> 'c -> 'b
3. (’T -> bool) -> ‘T list -> ‘T list
总结:
1. F#中每个函数都有一个返回值,这个返回值可以是具体的值,也可以是另一个函数(unit表示函数返回值为空(void)). 当在读一个函数接口说明时,最右面箭头"->"后面的部分,表示的就是该函数的返回值。F#中的每个函数都只能接受一个参数,同样,这个参数可以是具体的值,也可以是一个参数。
2. Higher-Order function 和Currying function是理解并能熟练运用函数式编程的重要基石,希望还不太明白的朋友能好好理解我上面的例子,把基础打好。
《结合实例学习F#》
1. 快速入门
2. 基本数据类型Discriminated Unions
3. 理解函数式语言中的函数