F#! and more

如果你还不知道 F# ,请到 http://msdn.com/fsharp 看个究竟吧! F# 最初是由 Microsoft Research 的 Don Syme 所开发,最新版本的F#已经发布了,现在就去下载2008年9月技术预览版吧,版本号是1.9.6。虽然还是 CTP 版本,不过 F# 也有不短的发展历史了,许多特性也非常有吸引力,如果你对面向对象有不满的地方,可能在F#里会找到非常好的解决办法。

Windows 平台的编程语言已经多得数不过来了,很大一部分是运行在 CLR 上的,CLR 的目标就是要在一个兼容的生态系统中将语言和 API 无缝集成在同一运行时中达到百花齐放、百家争鸣的效果,从而方便了任何有特殊爱好的程序员轻松选择自己钟意的语言开始快乐地coding,多么惬意的一件事啊。F# 就是这多种语言中的一种,它是一种函数化语言。

面向对象有什么问题?

面向对象是我们最熟悉的编程方式,从大学课程到培训,无一不把OO作为重头戏来开展。通过面向对象可以描述对象与绑定这些对象之间交互的约定所构成的环境。OO 利用类型约定、多态性以及精细的可见性等多种功能来提供出色的重用和封装特性。通常面向对象的语言采用静态类型系统,所以这些语言被称为静态类型语言,这意味着程序创建和使用的所有类型都在编译时进行检查;这可防止您对 Duck 类型的对象调用方法 Moo(在此方法不存在的情况下)。在代码运行之前,编译器可检测各种类型之间被破坏和误用的约定,从理论上讲,这样做可减少运行时错误。

但是 OO 也存在一些缺陷。类型安全性可能会使编程人员过分依赖编译器来捕获错误,而不是亲自创建必要的测试基础结构。同时,OO 还会促使编程人员预先定义自己的约定,而这往往是与快速原型编程和多用户编程环境背道而驰的。在大型软件项目中,组件之间的约定(通常由不同的团队所拥有)往往在整个开发周期中不断演变,这就要求约定的使用者不断更新其代码。由于上述这些问题,OO 语言可能显得有些复杂和冗长。

函数式编程将程序计算视为数学函数的计算。将两个数字相加即为一个函数。假定有两个输入值 5 和 10,则输出值为 15。为解决某个问题,函数式编程将该问题细分为多个可使用函数表示的较小的块,然后再将这些函数进行组合以生成预期的输出。

函数式编程通常会避开状态(类似于变量和对象等内容)和状态变异。这实际上是与 OO 相左的,后者的主要目的恰恰是为了创建和操作状态(对象)。由于避开了状态,函数式程序往往更加准确、精密而且可验证。这是由于它很少会产生不可预知的副作用——当程序(如某个变量)的内部状态在各操作之间发生变化时可能会产生一些副作用,这些副作用会导致在一个或多个操作中产生非预期的结果。面向对象的语言和动态语言依赖编程人员来封装和保护状态,以减少不可预知的副作用,因为这些副作用会不可避免地导致更多错误的发生。函数式语言可以是很纯粹的,也就是说没有任何副作用或状态。但是,大多数流行的函数式语言都具有状态操作功能,这有利于促进与外部 API、操作系统以及其他语言的互操作性。针对某些程序必须使用一定量的状态信息来表示问题这一事实,它们也有相应的考虑。
有一些很有用的功能,它们对函数式语言是通用的。高阶函数可以将另一个函数作为参数,并且返回结果可以是函数,这为代码重用提供了强大的支持。
下面的例子对数组或数据列表中的每个元素都执行(或映射)一个函数。
假定没有变异的状态(为返回的结果创建一个新数组),并且没有函数结果依赖于先前的结果,则现在即可开始考虑将这一映射示例扩展到多个处理内核(通过将数组分成两部分并将得到的两个数组连接起来)乃至多个计算机(跨 n 个计算机拆分序列化数据、将代码函数传递到计算机、在一个单独主机中执行并连接序列化结果),而不必担心诸如状态管理等并发操作问题。这就是最酷的地方噢!

 

Code
// increment function
let increment x = x + 1

// data
let data = [1 .. 5]

// map (func, myList)
let map func myList = { for x in myList -> func x }

print_any (map increment data)

第一行定义一个简单的递增函数,它有一个参数 x,此函数将计算 x + 1。然后,将数据定义为一个不可变异的列表,其中包含整数 1 到 5。此映射函数以函数和列表作为参数,使用传统编程中的喷淋方法来遍历列表并执行 func 函数参数,然后将列表中的当前元素传递给它。接下来,print_any 函数使用增量函数和数据列表作为参数来执行映射函数,随后将结果打印到屏幕。

 

类型在哪里?在本例中并不存在。实质上,变量(数据)实际被类型化为 System.Int32 的列表,但并不需要将此告知编译器,因为它使用类型推断功能即可推断出这一情况。编译器做的工作越多,您的工作就会越少。这听起来非常不错。使用列表推导功能,您在一行内就可以轻松地重写先前的部分代码:

Code
print_any { for x in 1..5 -> x + 1 }

另一个很酷的 F# 功能是模式匹配:

 

 

Code
let booleanToString x = match x with false -> "False" | _ -> "True"

此函数具有一个 Boolean 类型,此类型可与 false 或任何其他值匹配,并返回相应的字符串。如果向 booleanToString 函数传递一个字符串,会出现什么结果?同样,编译器承担了繁重的工作,将 x 参数的类型定义为类型 bool。它通过 x 在此函数中的用法推断出这一点(在这种情况下,它仅与 bool 类型相匹配)。
模式匹配还可以用于构建强大的函数调度机制,以便在 OO 及其他环境中轻松地重现虚拟方法调度。当您需要根据接收方(将在此对象中调用虚拟方法)和方法参数的变化来改变行为时,虚拟方法才真正开始起作用。访问者模式即是为了帮助解决这种情况而设计的,但是在 F# 中基本不需要(已包含在模式匹配中)。

 

支持用户定义的类型,通常由记录(类似于 OO 环境中的类)或聚合(通常是一种有序序列类型)提供。以下是用户定义的队员和球队记录:

 

Code
type player =
{ firstName :
string;
lastName :
string;
}

type soccerTeam =
{ name :
string;
members : player list;
location :
string;
}

延迟计算是另一种常见的函数式语言功能,其功能源自这样一种理论,即函数化编程中没有明显的副作用。延迟计算依赖于编译器和编程人员选择表达式计算顺序的能力,它可以使计算延迟到所需的时间点。编译器和编程人员都可以使用延迟计算技术作为精确的性能优化手段,因为它可以避免一些不必要的计算。在处理无限(或极大)数据集的计算时,它也是一种有用的技术。实际上,Microsoft Research 的 Applied Games 研究组曾在 F# 中使用此技术解析过数 TB 字节的 Xbox LIVE® 日志数据。

 

以下是 F# 中的延迟计算:

Code
let lazyTwoTimesTwo = lazy (2 * 2)
let actualValue = Lazy.force lazyTwoTimesTwo

此代码执行时,lazyTwoTimesTwo 只是在运行时充当指向执行 2 * 2 的函数的轻型指针。仅当实际强制执行此函数时,才能获得结果。
虽然函数化编程模型可能不太容易理解(可能需要 30-40 小时的准备时间),但一旦掌握它,您的编程能力就会有质的飞跃。您只需使用很少的代码就可以解决问题并减少错误。此模式通过最大程度减少意外的副作用来保护代码的安全,并通过执行积极的优化措施使其保持快速运行状态。此外,简单性往往代表出色的扩展性——您只需看一下 Map 代码,想想该代码如何能够轻松分布到成千上万台计算机中,就可以了解其中的缘由了。
至此,您已经看到了函数化编程的一些非常出色的功能,例如类型推断、高阶函数、模式匹配和用户定义类型等。

在hubFS有很多热门的讨论,推荐去看看http://cs.hubfs.net

 

posted @ 2008-09-09 23:35  亮小猪  阅读(364)  评论(0编辑  收藏  举报