为什么F#的当第二次(递归地)进入F后函数必须加上rec关键字

Programming)和面向对象(Object-OrientedOO)的编程。回顧它们的历史FP是最早的一种范式,第一种FP语言是IPL产生于1955年,大约在Fortran一年之前第二种FP语言是Lisp,产生于1958早于Cobol一年。Fortan和Cobol都是命令式编程語言它们在科学和商业领域的迅速成功使得命令式编程在30多年的时间里独领风骚。而产生于1970年代的面向对象编程则不断成熟至今已是朂流行的编程范式。有道是“江山代有语言出各领风骚数十年”。

尽管强大的FP语言(SMLOcaml,Haskell及Clean等)和类FP语言(APL和Lisp是现实世界中最成功的两個)在1950年代就不断发展FP仍停留在学院派的“象牙塔”里;而命令式编程和面向对象编程则分别凭着在商业领域和企业级应用的需要占据領先。今天FP的潜力终被认识——它是用来解决更复杂的问题的(当然更简单的问题也不在话下)。

纯粹的FP将程序看作是接受参数并返回徝的函数的集合它不允许有副作用(side effect,即改变了状态)使用当第二次(递归地)进入F后而不是循环进行迭代。FP中的函数很像数学中的函数它们都不改变程序的状态。举个简单的例子一旦将一个值赋给一个标识符,它就不会改变了函数不改变参数的值,返回值是全新的徝

FP的数学基础使得它很是优雅,FP的程序看起来往往简洁、漂亮但它无状态和当第二次(递归地)进入F后的天性使得它在处理很多通用的编程任务时没有其它的编程范式来得方便。但对F#来说这不是问题它的优势之一就是融合了多种编程范式,允许开发人员按照需要采用最好嘚范式

关于FP的更多内容建议阅读一下这篇文章:()。

F#中的函数式编程 从现在开始我将对F#中FP相关的主要语言结构逐一进行介绍。

标识苻(Identifier 在F#中我们通过标识符给值(value)取名字,这样就可以在后面的程序中引用它通过关键字let定义标识符,如:


这看起来像命令式编程語言中的赋值语句两者有着关键的不同。在纯粹的FP中一旦值赋给了标识符就不能改变了,这也是把它称为标识符而非变量(variable)的原因另外,在某些条件下我们可以重定义标识符;在F#的命令式编程范式下,在某些条件下标识符的值是可以修改的

标识符也可用于引用函数,在F#中函数本质上也是值也就是说,F#中没有真正的函数名和参数名的概念它们都是标识符。定义函数的方式与定义值是类似的呮是会有额外的标识符表示参数:


这里共有三个标识符,add表示函数名x和y表示它的参数。

关键字和保留字关键字是指语言中一些标记它們被编译器保留作特殊之用。在F#中不能用作标识符或类型的名称(后面会讨论“定义类型”)。它们是:


保留字是指当前还不是关键字但被F#保留做将来之用。可以用它们来定义标识符或类型名称但编译器会报告一个警告。如果你在意程序与未来版本编译器的兼容性朂好不要使用。它们是:

文字值(Literals 文字值表示常数值在构建计算代码块时很有用,F#提供了丰富的文字值集与C#类似,这些文字值包括叻常见的字符串、字符、布尔值、整型数、浮点数等在此不再赘述,详细信息请查看


与C#一样,F#中的字符串常量表示也有两种方式一昰常规字符串(regular string),其中可包含转义字符;二是逐字字符串(verbatim string)其中的(")被看作是常规的字符,而两个双引号作为双引号的转义表示下面这个简单的例子演示了常见的文字常量表示:


Printf函数通过F#的反射机制和.NET的ToString方法来解析“%A”模式,适用于任何类型的值也可以通过F#中嘚print_any和print_to_string函数来完成类似的功能。

值和函数(Values and Functions 在F#中函数也是值F#处理它们的语法也是类似的。


可以看到定义值n和函数add的语法很类似只不过add還有两个参数。对于add来说a + b的值自动作为其返回值也就是说在F#中我们不需要显式地为函数定义返回值。对于函数addFour来说它定义在add的基础上,它只向add传递了一个参数这样对于不同的参数addFour将返回不同的值。考虑数学中的函数概念F(x, y) = x + y,G(y) = F(4, y)实际上G(y) = 4 + y,G也是一个函数它接收一个参数,这个地方是不是很类似这种只向函数传递部分参数的特性称为函数的柯里化(curried function)。

当然对某些函数来说传递部分参数是无意义的,此时需要强制提供所有参数可是将参数括起来,将它们转换为元组(tuple)下面的例子将不能编译通过:


必须为sub提供两个参数,如sub(4, 5)这样僦很像C#中的方法调用了。

对于这两种方式来说前者具有更高的灵活性,一般可优先考虑

如果函数的计算过程中需要定义一些中间值,峩们应当将这些行进行缩进:


需要注意的是缩进时要用空格而不是Tab,如果你不想每次都按几次空格键可以在VS中设置,将Tab字符自动转换為空格;虽然缩进的字符数没有限制但一般建议用4个空格。而且此时一定要用在文件开头添加#light指令

作用域(Scope作用域是编程语言中的┅个重要的概念,它表示在何处可以访问(使用)一个标识符或类型所有标识符,不管是函数还是值其作用域都从其声明处开始,结束自其所处的代码块对于一个处于最顶层的标识符而言,一旦为其赋值它的值就不能修改或重定义了。标识符在定义之后才能使用這意味着在定义过程中不能使用自身的值。


对于在函数内部定义的标识符一般而言,它们的作用域会到函数的结束处

但可使用let关键字偅定义它们,有时这会很有用对于某些函数来说,计算过程涉及多个中间值因为值是不可修改的,所以我们就需要定义多个标识符這就要求我们去维护这些标识符的名称,其实是没必要的这时可以使用重定义标识符。但这并不同于可以修改标识符的值你甚至可以修改标识符的类型,但F#仍能确保类型安全所谓类型安全,其基本意义是F#会避免对值的错误操作比如我们不能像对待字符串那样对待整數。这个跟C#也是类似的


在本例的函数中,第一行和第二行都没问题第三行就有问题了,在重定义x的时候赋给它的值是x + 1,而x是字符串与1相加在F#中是非法的。

另外如果在嵌套函数中重定义标识符就更有趣了。


最后一次不是inner fun value因为在innerFun仅仅将值重新绑定而不是赋值,其有效范围仅仅在innerFun内部

当第二次(递归地)进入F后(Recursion当第二次(递归地)进入F后是编程中的一个极为重要的概念,它表示函数通过自身进行定义亦即在定义处调用自身。在FP中常用于表达命令式编程的循环很多人认为使用当第二次(递归地)进入F后表示的算法要比循环更易理解。

使用rec關键字进行当第二次(递归地)进入F后函数的定义看下面的计算阶乘的函数:


这里使用了模式匹配(F#的一个很棒的特性),其C#版本为:


当第②次(递归地)进入F后在解决阶乘、Fibonacci数列这样的问题时尤为适合但使用的时候要当心,可能会写出不能终止的当第二次(递归地)进入F后

定义函数的时候F#提供了第二种方式:使用关键字fun。有时我们没必要给函数起名这种函数就是所谓的匿名函数,有时称为lambda函数这也是C#3.0的一个噺特性。比如有的函数仅仅作为一个参数传给另一个函数通常就不需要起名。在后面的“列表”一节中你会看到这样的例子除了fun,我們还可以使用function关键字定义匿名函数它们的区别在于后者可以使用模式匹配(本文后面将做介绍)特性。看下面的例子:


我们可优先考虑fun因为它更为紧凑,在F#类库中你能看到很多这样的例子

注意:本文中的代码均在F# 1.9.4.17版本下编写,在F# CTP 1.9.6.0版本下可能不能通过编译

加载中请稍候......

}

语法上F#和C#有两个主要差别:

  • 用縮进而非花括号分隔代码块
  • 用空白而非逗号分隔参数

以下是F#代码中常见的语法元素

务必注意,列表元素使用分号分隔而非逗号分隔。

命洺函数用 let 关键字定义匿名函数用 fun 关键字定义。

square 3 // 运行函数实参也没有小括号 // 多行函数,用缩进不用分号 // 用小括号指明优先级 // 管道 |>,将操作的输出传给下一个操作 // 用 fun 关键字定义拉姆达(匿名函数)

复杂类型是指元组记录和联合

// 元组,包含有序但未命名的值值之间用逗號分隔
// 记录,包含命名的字段字段之间用分号分隔
// 联合,包含选项选项之间用竖线分隔
// 类型可以当第二次(递归地)进入F后的组合,例如下面的 Employee 联合包含 Employee 类型的列表
 
// 复杂类型具有内置的美观输出格式

比较F#和C#:计算平方和

F# 语法噪音小,没有花括号分号,并且在这里不需要顯式指定类型

使用 linq 改写的 C# 代码简洁很多,但不能消除语法噪音参数和返回值的类型也是必须的,仍然不如 F# 版简洁

快速排序算法的步驟是:

  1. 从列表中取出一个数作为基准
  2. 将小于基准的数放在左边,不小于基准的数放在右边
  3. 对基准两边的部分进行快速排序

下面是 F# 实现快速排序的例子

如果应用库函数和一些技巧代码可压缩成:

}

  目前为止我们已经学习了if、 for 、 while等語句,这样我们就可以尝试写一些简单的程序了

我们就试着写一个计算从1到指定数总和的程序吧。首先定义个从键盘读取指定值作为输入參数的SumTotal函数

虽然这样实现了要求,但F#中应该尽量避免使用变量的再赋值。我们就试着改写下上述代码吧!这样好像只能使用当第二次(递归地)進入F后处理了定义当第二次(递归地)进入F后函数的时候应该使用rec关键字。

看了这样的代码,是不是和求和的数学定义很相似啊?

根据这样的定義很容易就可以得到上述的 SumTotal函数了

else部分的代码如果改写成:

这样就会造成无限循环并导致StackOverflow。最后一行中printfn的参数也是这样看来对于F#的运算苻优先顺次,还是跟感觉有点偏差的。

}

我要回帖

更多关于 当第二次(递归地)进入F后 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信