8.1.4 在 F# 中使用函数列表

来源:互联网 发布:安工大网络接入系统 编辑:程序博客网 时间:2024/06/03 12:50

8.1.4 在 F# 中使用函数列表

 

首先,我们声明一个表示有关客户信息的类型;客户有很多属性,因此,用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)

 

无点风格编程应始终用在刀刃上。虽然它使代码更简洁、典雅,但是,有时很难阅读和推理,我们在这里的演示就很重要。无点风格对于某些领域的函数编程是重要的,在第十五章,我们将会看到它在开发特定域语言时的用途。

 

0 0