结合实例理解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 , 由于这不是本文的重点,所以略去不表)

这段代码是运行结果是
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 

F#函数式语言总结

1.F#函数式语言中每个函数都有一个返回值,这个返回值可以是具体的值,也可以是另一个函数(unit表示函数返回值为空(void)). 当在读一个函数接口说明时,最右面箭头"->"后面的部分,表示的就是该函数的返回值。F#中的每个函数都只能接受一个参数,同样,这个参数可以是具体的值,也可以是一个参数。

2. Higher-Order function 和Currying function是理解并能熟练运用函数式编程的重要基石,希望还不太明白的朋友能好好理解我上面的例子,把基础打好。

本文来自芭蕉博客园文章《结合实例实习F#(三)--理解函数式语言中的函数

【编辑推荐】

  1. F#入门:基本语法,模式匹配及List
  2. C# Actor的尴尬与F#美丽外表下的遗憾
  3. 函数式编程语言F#:基于CLR的另一个头等编程语言
  4. Visual Studio 2010爆F#二进制兼容性问题
  5. 推荐Visual Studio 2010中F#的一些资源
责任编辑:彭凡 来源: 博客园
相关推荐

2011-06-09 09:52:41

F#

2010-01-07 10:04:18

F#函数式编程

2009-11-09 17:51:51

F#函数式编程

2009-06-22 13:43:01

F#函数式编程

2010-07-07 13:11:20

ScalaF#C#

2010-07-09 14:12:00

ScalaF#C#

2010-07-19 10:01:57

Perl函数

2011-03-30 11:01:13

C语言随机

2019-07-11 08:00:00

JavaScriptJulia编程语言

2010-01-18 13:54:28

函数

2010-01-25 17:05:37

C++语言

2009-12-11 13:59:35

F#

2010-01-26 08:25:06

F#语法F#教程

2021-10-14 15:34:48

C语言字符串函数

2011-06-15 10:53:05

C语言

2009-12-11 10:44:00

Scala讲座函数 scala

2010-09-06 15:17:14

Sql函数

2010-04-07 16:51:59

F#

2022-01-06 14:25:24

C语言指针内存

2023-10-27 11:21:20

C语言Multics语言
点赞
收藏

51CTO技术栈公众号