F# 中的 LOP 使用简单代码描述复杂问题的优势体现在如下三个方面:
- 抽象表述—— F# 使你在代码中描述特定领域概念时,不需要引入新的抽象层。
- 具体表述—— F# 使你可使用其它语言描述问题,然后在 F# 中读取。
- 计算表述—— F# 使你使用其它语言编码处理本地概念时,不需要借用第三者更专业化的语言。
抽像表述,Abstract Representation。
使用现代面向对像语言的程序员面临着的难题是如何表述概念。C# 只便于描述“对像”及其行为,但却难于精确表达抽象思想。比如你可以很容易地构造出包含 Meow() 和 Purr() 方法的 Cat 类,但你如何在 C# 中描述“快乐”这个概念呢?
这里将通过“抽像表述”来聊一聊 F# 是如何自然而然地表述概念的。
类型缩写,Type Abbreviations
F# 可以创建 alias 来描述类型,在编译期会被替换成核心类型,也即类型缩写只存在于代码中。使用类型缩写在描述特定领域问题的概念时不需要引入或创建自定义类型。比如将 int 类型化名为 CustomerID,则使用 CustomerID 时含义清楚,而非仅引用 int 型时,还要猜这个整型值代表着什么。
类型缩写有助于处理更复杂的泛型类型。比如 Dictionary<string, string> 型,使用中可能会难于判断这两个 string 类型到底意味着什么而不好处理。使用类型缩写可以清楚表义:
识别联合体,Discriminated Unions
让我们来继续瘪一瘪 C#。比如用 C# 来表述 CardSuit 概念:
使用枚举类型来描述这一概念看似简单,实则隐患巨大。对于使用者来说,此枚举类型是否被 [Flags] 属性修饰过并不了解,枚举类型内枚举项的值是如何分配的也不了解。因此,如下两种用法,可轻易破坏 C# 中 enum 类型“只许有一个存在项”的语义,却可躲过 C# 编译期的类型检查,从而产生运行时异常:
为此程序员将不得不编写更为复杂的代码来处理隐患。《Effective Java》第 21 章对此有讨论。而 F# 中的描述方式可以避免这一隐患:
这便是 F# 的“识别联合体”的使用。不仅如此,F# 中的识别联合体还可以简便地强化项目类型,这是枚举类型望尘莫及的:
可选类型,Option Type
已经瘪了 C# 不少了,就不在乎再多瘪 C# 一把吧。这里不得不提一句 .net 平台下画蛇添足出来的东西—— null 值。考虑如下代码,获取你的宠物,将打印它的名字。
一样,简单易懂,却含有隐患。这里面 null 到底意味如何?它是特定问题领域里面必须的么?若我问你,你养树袋熊当宠物么,你会回答我“null”?不会,这个 null 只不过是人为制造出来用以在程序中表达某物不存在或者未初始化的。
然而在 LOP 的世界中,我们将避免用我们人类大脑无法理解的方式来表达思想。F# 使用“可选类型”方式来以更自然的方式表达“没有”的概念。
若返回 Some(…) 则表明有,否则返回 None。可选类型的另一大优势在于联系本意。C# 中调用“GetPet”方法时,无法判断当没有宠物时返回的是什么,是简单返回一个 null 呢?还是会扔出来一个异常?而在 F# 中这就很明确,没有就是 None。
参见此部分:可选类型在“部分活性模板”中的使用。
模式匹配,Pattern Matching
又是一个可以瘪 C# 的地方。C# 中有 switch - case 表达式,却只能 case 常数值。F# 中的模式匹配可以简便地处理更为复杂的情况。比如通过比较数据结构来确定链表长度的用法:
以下是常量与变量一起匹配处理的样例:
综上,F# 中的 LOP 咱是显摆了不少,C# 也被瘪了不少。其实 C# 的缺陷,也正是“命令式语言”的通病,只不过 C# 表现得更为严重;F# 的优势,也正是“函数式语言”的优势,而其是否强化了这一优势还需要实践去证明。就 C# 来说,从 C# 3.0 开始,它借鉴并引入了“函数式思维”,改良自己的不足,但本质上是“C# 体 F# 用”,治标不治本。LINQ 再强大也需要框在类的方法中去书写。C# 这种死板的 OOP 设计,在当时被吹得“包治百病”,却被时间和实践证明是损人不利己。反观就 F# 来说,作为函数式语言家庭出色的一员,它还有什么更吸引人的 Features,让我们慢慢道来。
评论