递归函数(Recursive Functions)
我们都知道,所谓递归函数就是函数自己调用自己,在函数式编程语言中,这是非常有用的。
在F#语言中, 定义一个递归函数,只需要在函数名前面有一个关键字rec来申明,比如下面拿一个经典的列子斐波那契数列来编写一个递归函数:
> let rec factorial x =
- if x <= 2 then
- 1
- else
- factorial (x-1) + factorial (x-2);;
val factorial : int -> int
> factorial 3;;
val it : int = 2
> factorial 10;;
val it : int = 55
在高阶函数中结合只用递归,你可以很容易地发现,在命令式编程语言中可以模拟循环结构语句到而不需要变量值。比如下面的代码中创建了两个普通的for和while循环。在这里要注意的是,在for循环中,times计数器的值是通过一个递归函数来实现的:
> let rec forLoop body times =
- if times <= 0 then
- ()
- else
- body()
- forLoop body (times - 1);;
val forLoop : (unit -> unit) -> int -> unit
> forLoop (fun () -> printfn "Looping ...") 3;;
Looping ...
Looping ...
Looping ...
val it : unit = ()
> let rec whileLoop predicate body =
- if predicate() then
- body()
- whileLoop predicate body
- else
- ();;
val whileLoop : (unit -> bool) -> (unit -> unit) -> unit
>
open System
whileLoop
(fun () -> DateTime.Now.DayOfWeek <> DayOfWeek.Saturday)
(fun () -> printfn "I wish it were the weekend...");;
I wish it were the weekend...
I wish it were the weekend...
I wish it were the weekend...
相互递归(Mutual recursion)
相互递归就是两个函数彼此相互调用对方。相互递归对于F#的类型推断来说,无疑是一个难题,因为为了确定第一个函数的类型,你需要知道第二个函数的类型, 反之亦然。
在下面的代码中,简单的编写两个函数,并且相互调用,会产生一个为定义的编译错误。从函数编程的规范来讲,在前面的函数是不能调用其后面的函数的:
> let isOdd x = if x = 1 then true else not (isEven (x -1))
- let isEven x = if x = 0 then true else not (isOdd (x-1));;
let isOdd x = if x = 1 then true else not (isEven (x -1))
-------------------------------------------^^^^^^
stdin(9,44): error FS0039: The value or constructor 'isEven' is not defined
所以,为了确定相互递归的函数能够正常工作,就必须使用关键字and,以便告诉F#编译器同时执行函数间的类型判断。下面的代码是改正后的函数:
> let rec isOdd n = (n = 1) || isEven (n - 1)
- and isEven n = (n = 0) || isOdd (n - 1);;
> isOdd 13;;
val it : bool = true
符号运算符(Symbolic Operators)
想想如果我们每次对类似1+2进行计算,不能通过+运算符而是通过自己编写的一个函数add来实现计算,那是一件多么困难的编程。幸运的是,在F#中不仅内置了像加法、减法这样的运算,还允许通过自定义运算符来实现一个更清洁、更优雅的代码。
符号运算符可以由!%&*+-./<=>?@^|~中的任意符号来表示(其中包括:只要不是第一个字符)。下面的代码定义了一个新函数!来计算数字的阶乘:
> let rec (!) x =
- if x <= 1 then 1
- else x * !(x-1);;
val ( ! ) : int -> int
> !5;;
val it : int = 120
默认情况下,当符号运算有一个以上的参数时中使用中缀表示法来表示,意思就是第一个参数在符号运算符之前,这也是使用过程中经常碰到的,比如下面的代码:
> open System.Text.RegularExpressions;;
> let (===) str (regex : string) =
- Regex.Match(str,regex).Success;;
val ( === ) : string -> string -> bool
> "The quick brown fox" === "The (.*) fox";;
val it : bool = true
符号预算符在它们的参数之前,也被称作为前缀符号,必须使用~、!、?等前缀作为函数名。在下面的代码中,函数~+++以~为前缀,并且调用时,应该写成~+++ 1 2 3而不是1 ~+++ 2 3,这样是你的符号运算函数更加的适应函数编程的风格。
> let (~+++) x y z = x + y + z;;
val ( ~+++ ) : int -> int -> int -> int
> ~+++ 1 2 3;;
val it : int = 6