首先,我们声明一个表示有关客户信息的类型;客户有很多属性,因此,用F# 的记录类型表示最自然的选择,我们在前一章已经看过。清单 8.4 显示了类型声明,和所创建样本客户的代码。
清单 8.4 Client 记录类型和样本值 (F# Interactive)
> type Client =
{ Name : string; Income : int;YearsInJob : int
UsesCreditCard : bool;CriminalRecord : bool };;
type Client = (...)
> let john =
{ Name = "John Doe";Income = 40000; YearsInJob = 1
UsesCreditCard = true;CriminalRecord = false };;
val john : Client
这里没有什么新东西,我们声明了一个类型,并创建它的实例。为使清单更短,我们在声明类型,和创新值时,都没有为每个属性使用单独一行;在F# 中这是有效的,但必须在属性之间加上分号。使用轻量级语法,编译器会自动在行尾加上分号(如果需要分号),但是,当需要分行时,编译器就无能为力了,必须明确写上分号。
清单 8.5 完成这个示例。首先,创建测试列表,然后,确定是否提供贷款给前面清单中的样本客户(John Doe) 。
清单 8.5 执行测试 (F# Interactive)
> let tests = <-- 创建测试列表
[ (fun cl ->cl.CriminalRecord = true);
(funcl -> cl.Income < 30000);
(funcl -> cl.UsesCreditCard = false);
(funcl -> cl.YearsInJob < 2) ];;
val tests : (Client -> bool) list [1]
> let testClient(client) =
let issues = tests|> List.filter (fun f -> f (client)) [2]
let suitable =issues.Length <= 1 | 统计问题数,
printfn"Client: %s\nOffer a loan: %s (issues = %d)" client.Name | 输出结果
(if (suitable) then "YES" else "NO") issues.Length;; |
val testClient : Client –> unit
> testClient(john);;
Client: John Doe
Offer a loan: YES (issues = 1)
这是使用lambda 函数写初始化测试创建列表的常规语法,不必写出任何类型批注,F# 仍然能够正确推断出列表的类型[1]。F# 的类型推断非常智能,使用访问成员的名字就能推断出我们想要使用的记录类型。
在 C# 版本中,我们使用 Count 方法统计测试失败的数量;F# 没有对等的函数,我们既可以实现一个,也可以组合其他标准的函数,得到相同的结果。这里,我们采用第二种方法。首先,我们得到被认为是不安全客户的测试列表,可以通过使用 List.filter 返回结果为 true 的测试,然后,使用Length 属性,得到问题的数量。
在本节,我们学习了如何设计和使用基本的面向行为的数据结构,函数列表(a list of functions),在 C# 和 F# 中都有。在补充材料“无点式编程(Point-freeprogramming style)”中,我们会看到清单8.5 中用到的重要的函数技术。在下一节,我们会继续有关常见做法的讨论,就像我们讨论两个面向对象的设计模式和相关函数式结构一样。
无点式编程(Point-freeprogramming style)
我们见过很多例子,调用高阶函数时,不必显式写出 lambda 函数,那么,在清单 8.5 中这样做也行吗?程序的这种写法称为无点(point-free),因为我们在使用包含值(比如列表)的数据结构时,从来没有给结构中的值指定任何名字(特定的“点”)。我们用示例来演示已经见过的概念:
[1 .. 10] |> List.map ((+) 100)
places |> List.map (snd >>statusByPopulation)
第一种情况,我们处理一个数字集合,但是,没有任何符号表示列表中的值;第二种情况有点类似,只是处理的列表是元组,而且,没有用任何符号来表示元组或元组中的任何元素。
无点风格之所以可能,是由于有几项编程技术。第一行使用了散函数应用,这种方法是基于有大量参数的函数,创建有必要数量参数的函数。在我们的示例中,我们把中缀运算符 (+) 也看做普通函数。第二行使用了函数组合,这是另一项重要的构建函数技术,不必显式引用函数处理的值。
现在,我们看一下如何重写清单8.5 的示例。首先,我们要用管道运算符来重写 lambda 函数。
把:(fun f ->f client)
重写成:(fun f-> client |> f)
这两个函数意思相同。我们几乎完成,因为管道运算符把 client 作为第一个参数值,把函数作为第二个参数值。如果我们使用散应用来只指定第一个参数值(client),将得到一个函数,参数值为函数 (f),并将它应用到 client:
tests |> List.filter ((|>) client)
无点风格编程应始终用在刀刃上。虽然它使代码更简洁、典雅,但是,有时很难阅读和推理,我们在这里的演示就很重要。无点风格对于某些领域的函数编程是重要的,在第十五章,我们将会看到它在开发特定域语言时的用途。