本文共 8671 字,大约阅读时间需要 28 分钟。
模式匹配(Pattern Matching)
模式匹配允许你根据标识符值的不同进行不同的运算。有点像一连串的if...else结构,也像C++和C#中的switch,但是它更为强大和灵活。 看下面Lucas序列的例子,Lucas序列定义跟Fibonacci序列一样,只不过起始值不同:Code let rec luc x = match x with | x when x <= 0 -> failwith "value must be greater than zero" | 1 -> 1 | 2 -> 3 | x -> luc(x - 1) + luc(x - 2) printfn "(luc 2) = %i" (luc 2) printfn "(luc 6) = %i" (luc 6)
Output (luc 2) = 3 (luc 6) = 18
匹配规则时按照它们定义的顺序,而且模式匹配必须完整定义,也就是说,对于任意一个可能的输入值,都有至少一个模式能够满足它(即能处理它);否则编译器会报告一个错误信息。另外,排在前面的规则不应比后面的更为“一般”,否则后面的规则永远不会得到匹配,编译器会报告一个警告信息,这种方式很像C#中的异常处理方式,在捕获异常时,我们不能先不会“一般”的Exception异常,然后再捕获“更具体”的NullReferenceException异常。
可以为某个模式规则添加一个when卫语句(guard),可将when语句理解为对当前规则的更强的约束,只有when语句的值为true时,该模式规则才算匹配。在上例的第一个规则中,如果没有when语句,那么任意整数都能够匹配模式,加了when语句后,就只能匹配非正整数了。对于最简单的情况,我们可以省略第一个“|”:
Code let boolToString x = match x with false -> "False" | _ -> "True"
另一个有用的特性是,我们可以合并两个模式规则,对它们采取相同的处理方式,这个就像C#中的switch…case结构中可以合并两个case一样。
Code let stringToBool x = match x with | "T" | "True" | "true" -> true | "F" | "False" | "false" -> false | _ -> failwith "Invalid input."
Code let myOr b1 b2 = match b1, b2 with | true, _ -> true | _, true -> true | _ -> false let myAnd p = match p with | true, true -> true | _ -> false
模式匹配的常见用法是对列表进行匹配,事实上,对列表来说,较之if…then…else结构,模式匹配的方式更好。看下面的例子:
Code let listOfList = [[2; 3; 5]; [7; 11; 13]; [17; 19; 23; 29]] let rec concatenateList list = match list with | head :: tail -> head @ (concatenateList tail) | [] -> [] let rec concatenateList2 list = if List.nonempty list then let head = List.hd list in let tail = List.tl list in head @ (concatenateList2 tail) else [] let primes = concatenateList listOfList print_any primes
Code let pair = true, false let b1, b2 = pair let _, b3 = pair let b4, _ = pair
进一步分析,元组是一种类型,但是我们并没有显式地使用type关键字来声明类型,pair本质上是F#中Tuple类的一个实例,而不是自定义类型。如果需要声明自定义类型,就要使用type关键字了,最简单的情况是给已有类型起个别名:
Code type Name = string // FirstName, LastName type FullName = string * string
记录(Record)类型与元组有相似之处,它也是将多个类型的值组合为同一类型,不同之处在于记录类型中的字段都是有名称的。看下面的例子:
Code type Organization = { Boss : string; Lackeys : string list } let family = { Boss = "Children"; Lackeys = ["Wife"; "Husband"] }
Code type Company = { Boss : string; Lackeys : string list } let myCom = { new Company with Boss = "Bill" and Lackeys = ["Emp1"; "Emp2"] }
Code type recipe = { recipeName : string; ingredients : ingredient list; instructions : string } and ingredient = { ingredientName : string; quantity : int } let greenBeansPineNuts = { recipeName = "Green Beans & Pine Nuts"; ingredients = [{ingredientName = "Green beans"; quantity = 200}; {ingredientName = "Pine nuts"; quantity = 200}]; instructions = "Parboil the green beans for about 7 minutes." } let name = greenBeansPineNuts.recipeName let toBuy = List.fold_left (fun acc x -> acc + (Printf.sprintf "\t%s - %i\r\n" x.ingredientName x.quantity)) "" greenBeansPineNuts.ingredients let instructions = greenBeansPineNuts.instructions printf "%s\r\n%s\r\n\r\n\t%s" name toBuy instructions
Code type couple = { him : string; her : string } let couples = [ { him = "Brad"; her = "Angelina" }; { him = "Becks"; her = "Posh" }; { him = "Chris"; her = "Gwyneth" } ] let rec findDavid list = match list with | { him = x; her = "Posh" } :: tail -> x | _ :: tail -> findDavid tail | [] -> failwith "Couldn't find David" print_string(findDavid couples)
Code type Volume = | Liter of float | UsPint of float | ImperialPint of float
Code let vol1 = Liter 2.5 let vol2 = UsPint 2.5 let vol3 = ImperialPint 2.5
Code let convertVolumeToLiter x = match x with | Liter x -> x | UsPint x -> x * 0.473 | ImperialPint x -> x * 0.568
对于类型参数化,F#中有两种语法予以支持。来看第一种:
OCaml-Style type 'a BinaryTree = | BinaryNode of 'a BinaryTree * 'a BinaryTree | BinaryValue of 'a let tree1 = BinaryNode( BinaryNode(BinaryValue 1, BinaryValue 2), BinaryNode(BinaryValue 3, BinaryValue 4))
.NET-Style type Tree<'a> = | Node of Tree<'a> list | Value of 'a let tree2 = Node( [Node([Value "One"; Value "Two"]); Node([Value "Three"; Value "Four"])])
Code exception SimpleException exception WrongSecond of int // Hour, MInute, Second exception WrongTime of int * int * int
Code let testTime() = try let now = System.DateTime.Now in if now.Second < 10 then raise SimpleException elif now.Second < 30 then raise (WrongSecond now.Second) elif now.Second < 50 then raise (WrongTime (now.Hour, now.Minute, now.Second)) else failwith "Invalid Second" with | SimpleException -> printf "Simple exception" | WrongSecond s -> printf "Wrong second: %i" s | WrongTime(h, m, s) -> printf "Wrong time: %i:%i:%i" h m s | Failure str -> printf "Error msg: %s" str testTime()
与C#类似,F#也支持finally关键字,它当然要与try关键字一起使用。不管是否有异常抛出,finally块中的代码都会执行,在下面的例子中使用finally块来保证文件得以正确地关闭和释放:
Code let writeToFile() = let file = System.IO.File.CreateText("test.txt") in try file.WriteLine("Hello F# Fans") finally file.Dispose() writeToFile()
Code let sixtyWithSideEffect = lazy(printfn "Hello, sixty!"; 30 + 30) print_endline "Force value the first time:" let actualValue1 = Lazy.force sixtyWithSideEffect print_endline "Force value the second time:" let actualValue2 = Lazy.force sixtyWithSideEffect
Code Force value the first time: Hello, sixty! Force value the second time:
小节
本文继续讨论F#函数式编程范式的核心内容,主要是模式匹配、自定义类型、异常处理和延迟求值等内容,至此,F#的函数式编程的相关内容就介绍完了。模式匹配可以很大程度上简化我们的程序;自定义类型则可以帮助我们更好地组织程序;延迟求值不仅能够提升性能,还可用于创建无限的数据结构,比如自然数序列。另外,在开发F#程序时,建议常用Reflector来看看编译后代码的样子,来了解它优雅的函数式编程背后到底是什么。在下一站,我们将看看命令式编程的风景。
参考:
《Foundations of F#》 by Robert Pickering 《Expert F#》 by Don Syme , Adam Granicz , Antonio Cisternino 《》本文转自一个程序员的自省博客园博客,原文链接:http://www.cnblogs.com/anderslly/archive/2008/09/06/fs-adventure-fp-part-three.html,如需转载请自行联系原作者。