F#语言基于函数编程语言概念。函数编程语言将计算看做是数学函数的赋值,对于需要使用大量数学符号的某些领域专业人员来说,F#语言的数学特性颇具吸引力。Somasegar表示,F#的目标是成为.Net上的“上等公民”。
微软研究院也曾对F#语言做出过解释,F#语言集安全、性能、脚本与Modern Runtime系统(Java虚拟机和微软通用Runtime)等多种优势于一体,并支持Python等交互式脚本语言、强类型推理、ML的安全性。另外,F#语言可以访问access.net库以及其他数据库工具软件。
c#面向对象的
使用 .NET Framework 中的函数式编程技术
F# :http://msdn.microsoft.com/zh-cn/fsharp/default(en-us).aspx
本文讨论:
安装 F#
F# 语言基础
.NET 互操作性
异步 F#
作为 Microsoft? .NET Framework 家族的新成员,F# 提供类型安全、性能以及类似脚本语言的工作能力,所有这些都是 .NET 环境的一部分。此函数式语言由 Microsoft 研究院的 Don Syme 发明,作为 CLR 的 OCaml 语法兼容变体,但 F# 已经迅速地从科研转为投入实际应用。
随着函数式编程的概念通过 .NET 泛型和 LINQ 等技术越来越多地渗入主流语言(例如 C# 和 Visual Basic?),F# 在 .NET 社区里的知名程度也不断提高——因此,2007 年 11 月 Microsoft 宣布将 F# 确定为受支持的 .NET 编程语言。
多年来,大家一直认为函数式语言领域(ML、Haskell 等)更适合用于学术研究,而不适用于专业开发。但这并不代表这些语言没有过人之处。事实上,.NET 的一些重要的功能增强(例如泛型、LINQ、PLINQ 和 Futures)都是将一些函数式编程概念全新应用到语言所致。以往对这些语言的关注程度不高主要是因为它们的目标平台与专为 Windows? 编写程序的开发人员关系不大、不能与底层平台很好集成,或者不支持关系数据库访问、XML 解析和进程外通信机制等主要功能。
但是,CLR 及其“多种语言,单一平台”的方法将使此类语言在 Windows 开发中的应用越来越广泛。并且顺理成章地引起在一线工作的程序员们的注意。F# 即是这样一门语言。在本文中,我将为您介绍一些 F# 的基本概念和优点。然后,为了帮助您初步了解 F#,我将详细介绍它的安装过程并编写几个简单的小程序。
为什么要使用 F#?
对于小部分 .NET 程序员来说,学习一门 .NET Framework 函数化语言无疑将使自己在编写功能强大软件方面前进一大步。而对其他程序员来说,学习 F# 的理由就因人而异了。F# 能为开发人员提供哪些益处?
随着多核 CPU 的普及,安全并发程序已成为过去三年来的关注焦点。(1)函数式语言倡导一种固定不变的数据结构,可在线程和机器之间传递,而无需担心线程安全或原子访问,开发人员可以利用这一特点支持并发操作。(2)函数式语言还可更轻松地编写更支持并发特性的库,如稍后将在本文中介绍的 F# 异步工作流。
尽管对于专攻面向对象开发的程序员而言,可能对这种语言感觉不是这么强烈,但在很多情况下,函数式程序确实可以简化某些应用程序的编写和维护。例如,编写一个将 XML 文档转换成其他格式数据的程序。虽然完全可以通过编写一个 C# 程序,让它解析整个 XML 文档并应用各种 if 语句确定在文档中的不同位置采取何种措施,但实际上更好的方法是编写可扩展样式表语言转换 (XSLT) 程序。当然,XSLT 肯定包含大量的内置函数机制,如同 SQL 一样。
(3)F# 强烈建议不要使用空值 (null),而是提倡使用固定不变的数据结构。这些特性可以减少需要编写的特例代码量,从而有助于降低编程出错的频率。
(4)使用 F# 编写的程序还更加简洁。您可以切实地从两方面减少键入的内容:击键次数更少并且必须要向编译器通告变量类型、参数或返回类型的位置点也更少。这意味着需要维护的代码将大大减少。
(5)F# 具有与 C# 相似的性能特点。但是,与简洁程度相似的语言(特别是那些动态和脚本语言)相比,它的性能特点要好得多。并且,F# 也包含通过编写程序段并交互式执行查看数据的工具,这一点与许多动态语言类似。
安装 F#
F# 可从 research.microsoft.com/fsharp/fsharp.aspx 免费下载,它不仅会安装所有命令行工具,而且还会安装 Visual Studio? 扩展软件包,该软件包提供彩色语法突出显示、项目和文件模板(包括作为入门指南的详细 F# 示例代码)以及 IntelliSense? 支持。同时它还提供可在 Visual Studio 内部运行的 F# 交互式 shell,它使开发人员能够从源文件窗口中提取表达式、将表达式粘贴到交互式 shell 窗口,并立即得到代码段的结果——在类似增强的 Immediate 窗口中显示结果。
在我撰写本专栏时,F# 在 Visual Studio 内作为外部工具运行,所以它缺少某些开发人员能够从 C# 或 Visual Basic 中获得的无缝集成的能力。此外,F# 还缺少 ASP.NET 页面设计器支持。(这并不是说 F# 不能在 ASP.NET 中使用,完全不是。这仅表示 Visual Studio 并没有为 F# 提供类似 C# 和 Visual Basic 的那种现成拖放式开发体验。)
尽管如此,当前版本的 F# 还是可以在能够使用其他 .NET 兼容语言的任何位置使用。在接下来的几页中,您将看到一些示例。
您好,F#
介绍任何语言的特有方式就是通过那几乎成为标准的“Hello, World”程序。F# 也不例外:
复制代码
printf "Hello, world!"
虽然不能引起您太大的兴趣,但这个很小的例子显示出 F# 属于不需要显式入口点(C#、Visual Basic 和 C++/CLI 都需要显式入口点)的语言;该语言假设程序的第一行即为入口点并将从此处开始执行。
要运行此程序,刚入门的 F# 开发人员有两个选择:编译或解释。在 F# 解释程序中运行此程序 (fsi.exe) 很简单。只需从命令行启动 fsi.exe 并在出现的提示中输入上面一行内容即可,如图 1 所示。
图 1在 F# 解释程序中运行 'Hello, World' (单击该图像获得较大视图)
请注意,在 shell 中,每条语句必须以两个分号结尾。这是交互模式的特殊要求,编译过的 F# 程序并不需要使用这种方式。
要将此示例作为标准的 .NET 可执行程序运行,请正常启动 Visual Studio 并创建一个新的 F# 项目(可以在“其他项目类型”中找到这个项目)。最初,F# 项目包含一个 F# 源文件,称为 file1.fs。打开此文件将发现大量示例 F# 代码集合。浏览一下这些内容即可大概了解 F# 的语法结构。然后将整个文件替换成前面所显示的 "Hello, world!" 代码。运行此应用程序,毫无疑问,在控制台应用程序窗口中将出现“Hello, world!”。
如果更喜欢命令行方式,可以使用 F# 安装目录中 \bin 子目录里的 fsc.exe 工具编译这段代码。请注意:fsc.exe 与大多数命令行编译器的工作方式相似,它从命令行获取源代码并生成可执行程序作为结果。大多数命令行开关都有相关文档,如果曾经使用过 csc.exe 或 cl.exe 编译器,那您可能已经熟悉其中许多开关。不过请注意,F# 目前对 MSBuild 的支持尚不完美;在当前安装版本(撰写本文时为 1.9.3.7)中不能直接支持由 MSBuild 驱动的编译。
如果希望“Hello, world!”中更具图形化特点,F# 可以通过 CLR 平台(包括 Windows Forms 库)轻松地提供完整的逼真度和互操作性。尝试以下代码:
复制代码
System.Windows.Forms.MessageBox.Show "Hello World"
利用 .NET Framework 类库和 F# 库的能力使得 F# 语言不仅对那些早已使用如 OCaml 或 Haskell 之类函数式语言进行数学和科学计算的社区极具吸引力,而且受到全世界现有 .NET 开发人员的青睐。
Let 表达式
让我们看看比传统“Hello, world!”更复杂一些的 F# 代码示例。请看以下代码:
复制代码
let results = [ for i in 0 .. 100 -> (i, i*i) ]
printfn "results = %A" results
在该 F# 语法中,let 表达式是令人产生好奇的元素。它是整个语言中最重要的表达式。更正式地说,let 可以为标识符赋值。用 Visual Basic 和 C# 开发人员的行话来说,“它可以定义一个变量”。但这并不确切。在 F# 中,标识符包含两个要素。首先,标识符一旦定义就不能再更改。(这即是 F# 帮助程序员创建并发安全程序的方法,因为它不提倡可变的状态。)第二,标识符不仅可以是基元类型或对象类型(如 C# 和 Visual Basic 中所使用的类型),而且还可以是函数类型,这一点与 LINQ 相似。
同时还请注意,标识符从不显式定义为类型。例如,从不定义结果标识符;它将从后面表达式的右侧进行推断。这称为类型推断,并且它代表编译器分析代码、确定返回值和自动插入返回值的能力(这与新的 C# 推断类型表达式通过变量关键字进行推断的方法类似)。
Let 表达式不仅可以处理数据,还可以使用它来定义函数,F# 将函数看作第一级概念。下面的示例定义了一个加法函数,它使用两个参数 a 和 b:
复制代码
let add a b =
a + b
完全按照您预期的方式工作:将 a 与 b 相加,并将结果显式返回给调用者。这意味着从技术上讲,F# 中的每个函数都将返回一个值,即使返回的不一定是值,也会返回一个特殊的名称 unit。这将在 F# 代码中产生一些有趣的暗示,特别是与 .NET Framework 类库相交的部分。但目前,C# 和 Visual Basic 开发人员可以把 unit 大致看作是与 void 相同的类型。
有时函数应该忽略传递给它的参数。要在 F# 中达此目的,仅需使用下划线作为该参数的占位符即可:
复制代码
let return10 _ =
add 5 5
// 12 is effectively ignored, and ten is set to the resulting
// value of add 5 5
let ten = return10 12
printf "ten = %d\n" ten
与许多函数式语言类似,F# 允许根据其调用进行 currying(可以仅部分定义函数的应用),以便提供其余的参数:
复制代码
let add5 a =
add a 5
在某种程度上,这与创建一个接受不同的参数集并调用其他方法的重载方法相似:
复制代码
public class Adders {
public static int add(int a, int b) { return a + b; }
public static int add5(int a) { return add(a, 5); }
}
但两者还是有一点细微的差别。请注意,在 F# 版本中,没有显式定义类型。这表示编译器将采用自己的类型推断方法确定 add5 的参数是否是与加上整数 5 兼容的类型,并确定是按照这种方式编译,还是将其标记为错误。事实上,F# 语言主要使用隐式类型参数化(即使用泛型)。
在 Visual Studio 中,将指针停放在前面所显示的 ten 的定义上时,将表明其类型声明为:
val ten : ('a -> int)
在 F# 中,这表示 ten 是一个值,一个获取任意类型的参数并产生整数结果的函数。这种记号语法大致等同于 C# 中的 <T> 语法,所以对 C# 函数最贴切的说法是:ten 是类型参数化方法的委托实例,您想要忽略其类型(但在 C# 规则下不可忽略):
delegate int Transformer<T>(T ignored);
public class App
{
public static int return10(object ignored) { return 5 + 5; }
static void Main()
{
Transformer<object> ten = return10;
System.Console.WriteLine("ten = {0}", return10(0));
}
}
关键字 For
现在让我们看一下第一个示例中的 for 关键字:
#light
let results = [ for i in 0 .. 100 -> (i, i*i) ]
printfn "results = %A" results
先看代码的顶部,注意 #light 语法。这是为非 OCaml 程序员开始使用 F# 而做的让步,放宽某些 OCaml 语言的语法要求,并使用大量空白定义代码块。虽然不一定必要,但对于那些原本使用 C# 或 Visual Basic 的普通开发人员来说,它确实使语法更易于解析,因此它常出现在 F# 示例和公开的代码段中,并已成为事实上的 F# 编程标准。(未来版本的 F# 可能会将 #light 确定为默认语法,代替其他类似方法。)
那个看似简单的 for 循环实际上并不简单。正式地说,这是生成列表,即将生成列表型结果的代码块的另一种说法。
列表是函数式语言中经常出现的一种原语结构,在这方面它与数组有许多相似之处。但是,列表不允许基于位置的访问(如 C# 中传统的 a 语法)。列表可在函数式编程的不同位置出现,大多数情况中,可以将其看作是 F# 中与 .NET Framework 中的 List<T> 类似,但提供一些增强功能的同等项。
列表通常使用一些特殊类型,本例中标识符结果是聚合列表,特别地是 F# 将此聚合类型标识为类型 (int * int)。如果将此聚合列表看作是 SQL 中 SELECT 语句返回的一对列,它的含义就清楚多了。因此,示例本质上是创建一个包含 100 个项目的整数对列表。
通常,在函数式语言中,函数定义可以在出现代码的任何位置使用。因此,如果希望扩展前面的示例,可以编写以下代码:
let compute2 x = (x, x*x)
let compute3 x = (x, x*x, x*x*x)
let results2 = [ for i in 0 .. 100 -> compute2 i ]
let results3 = [ for i in 0 .. 100 -> compute3 i ]
遍历列表(或数组或其他一些重复结构)是函数式语言中很常见的任务,它已成为基本方法调用:List.iter。它仅简单地对列表中的每个元素调用一个函数。其他类似的库函数还可提供一些非常有用的功能。例如,List.map 将函数作为参数,并将该函数应用于列表中的每个元素,并返回该过程产生的新列表。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工
作,每周日13:00-18:00直播授课