6.7.2.1 处理列表
6.7.2.1 处理列表
我们看一个有关使用筛选和映射更大的演示样例。在 F# 库中的两个函数适用于各种集合类型,但我们将仅仅用它来处理我们已经非常熟悉的列表;在 C# 中,这些方法可用于不论什么实现了 IEnumerable<T> 接口的集合,所以。我们将使用泛型 .NET List <T> 类。
清单 6.21 显示了我们将要处理数据的初始化。
清单 6.21 有关城市人口的数据 (C# and F#)
// C# version using a simple class
class CityInfo { [2]
publicCityInfo(string name, int population) {
Name = name; Population = population;
}
public string Name { get; private set; }
public int Population { get; private set; }
}
var places = new List<CityInfo> { newCityInfo("Seattle", 594210), [3]
newCityInfo("Prague", 1188126), new CityInfo("New York",7180000),
newCityInfo("Grantchester", 552), new CityInfo("Cambridge",117900) };
// F# version using tuples
> let places = [1]
[("Seattle", 594210); ("Prague", 1188126); ("NewYork", 7180000);
("Grantchester", 552); ("Cambridge", 117900) ];;
val places : (string * int) list
在 F# 中,我们将使用常见的样例,有关城市名字和人口的信息的列表[1]。尽管,我们能够把 F# 元组转换成我们已经实现的 Tuple 类,但这一次,我们会使用更典型 C# 表示。声明CityInfo 类[2]。用它创建包括城市信息的列表[3]。
在 C# 中,我们可以使用在 .NET 3.5 中的 Where 和 Select 方法处理数据。两者都是扩展方法,因此,可以使用通常的点表示法来调用:
var names =
places.Where(city => city.Population > 1000000)
.Select(city => city.Name);
另外。这也展示了使用高阶操作的优点。作为參数值给定的 lambda 函数,描写叙述了筛选的条件(第一种情况)。或为每一个城市(另外一种情况)指定返回值。这是我们必须指定的。但不须要了解集合的底层结构。也不指定应该怎样获得结果,全部这些被封装在高阶操作中。
在 F# 中运行同样的操作。首先,想要筛选数据集。然后,仅仅选择城市的名字。我们能够通过调用 List.filter,并用结果作为给 List.map 函数的最后參数。能够发现。这看起来非常丑陋,且难于阅读:
let names =
List.map fst
(List.filter (fun (_, pop) -> 1000000 < pop) places)
当然,F# 能够做得更好。
前面的 C# 版本号之所以优雅,是由于编写的操作,与运行(先筛选,再映射)的顺序同样,能够把一个操作写在单独的一行上。在 F# 中,通过使用管道。能够得到同样的代码布局:
let names =
places |> List.filter (fun (_, pop) -> 1000000 < pop)
|> List.map fst
在这里,管道运算符首先将左边的值(places)传递给右边的筛选函数。下一步。把前面的操作结果传递给下一个操作(映射)。虽然我们使用这个运算符有相当一段时间了。但这个演示样例终于展示了为什么会被称为“管道”。数据元素依次通过管道进行处理。管道是由使用管道运算符,把几个操作链接起来创建的。
注意。操作的顺序有时重要,有时不重要。这里,我们必须先运行筛选;假设在第一步先做映射。得到的列表仅包括城市名字。就不会有人口的信息。而这是筛选所须要的。
在 F# 中写列表处理函数,能够把管理和其它函数式技术组合起来。比方散函数应用和函数组合。我们简单地看一下写处理代码时,下一个步应该做什么:
let names =
places |> List.filter (snd >> ((<) 1000000))
|> List.map fst
使用函数组合生成筛选函数。而没有显式使用 lambda 表达式。第一个函数是 snd,它返回元组的第二个元素。在这里,它表示人口。
在组合中使用的第二个函数。是散应用运算符。我们仅指定第一个參数值。因此。会得到一个函数。当第二个參数值大于给定数字时,返回 true。
提示
写代码(不仅仅是在函数风格中)时,应该始终考虑到。在以后须要对其进行改动时,理解代码是非常困难的。在前面的演示样例中。使用函数组合的版本号并非特别简短,看上去也不更优雅;其实,我们认为其可读性比显式使用 lambda 函数的版本号要差,因此。在这样的情况下。我们更倾向于使用 lambda 表示法。非常多情况下。函数组合能显著简化代码。不幸的是,没有简单的规则能够遵循,我们能提供的最好忠告,是使用常识,并想象有其它人试图理解这段代码。
处理集合数据是我们常常要做的任务,因此,编程语言的设计者都努力使其尽可能easy;如今,C# 和 F# 都提供了更方便的方式,来解决我们刚才用高阶函数实现的这个任务。
理解高阶函数的原理是必要的。由于。它能处理不论什么数据结构。而不不过列表。
C# 3.0 的查询表达式和 F# 的序列表达式
你可能已经看到过,在 C# 中使用查询表达式(query expressions)写数据查询的演示样例。使用这个功能写我们前面的代码,看起来像这样:
var names = from p in places
where 1000000 < p.Population
select p.Name
这常常作为一项关键的新功能来演示的,但它不会没有底层机制,比方 lambda 函数和高阶操作。我们已经关注使用这些,由于为学会显式使用它们时,可以用类似的函数式方法来处理不论什么数据,而不仅是集合。
简化的语法很实用。在 F# 中也有类似的功能,称为序列表达式(sequence expressions)。我们将在第十二章讨论有关内容,但对于好奇的人来说。以下是用 F# 写的相同查询:
let names =
seq{ for (name, pop) in places do
if (1000000 < pop) then yield name }
它看起来很像括起来。并用 seq 标记的普通代码块。这是有益的。由于在 F# 中,它是更通用的语言构造。也能用于处理其它值。在第十二章,我们将看到用它来处理选项值。也会看到 C# 查询表达式能用于类似的目的。
我们已经看过使用两个最常见的列表处理函数。它们很实用,如今要深入了解第三个这种函数。并自己来实现。