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 的普及,安全并发程序已成为过去三年来的关注焦点。函数式语言倡导一种固定不变的数据结构,可在线程和机器之间传递,而无需担心线程安全或原子访问,开发 人员可以利用这一特点支持并发操作。函数式语言还可更轻松地编写更支持并发特性的库,如稍后将在本文中介绍的 F# 异步工作流。
尽 管对于专攻面向对象开发的程序员而言,可能对这种语言感觉不是这么强烈,但在很多情况下,函数式程序确实可以简化某些应用程序的编写和维护。例如,编写一 个将 XML 文档转换成其他格式数据的程序。虽然完全可以通过编写一个 C# 程序,让它解析整个 XML 文档并应用各种 if 语句确定在文档中的不同位置采取何种措施,但实际上更好的方法是编写可扩展样式表语言转换 (XSLT) 程序。当然,XSLT 肯定包含大量的内置函数机制,如同 SQL 一样。
F# 强烈建议不要使用空值 (null),而是提倡使用固定不变的数据结构。这些特性可以减少需要编写的特例代码量,从而有助于降低编程出错的频率。
使 用 F# 编写的程序还更加简洁。您可以切实地从两方面减少键入的内容:击键次数更少并且必须要向编译器通告变量类型、参数或返回类型的位置点也更少。这意味着需要 维护的代码将大大减少。
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[i] 语法)。列表可在函数式编程的不同位置出现,大多数情况中,可以将其看作是 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 将函数作为参数,并将该函数应用于列表中的每个元素,并返回该过程产生的新列表。

 

管道
让 我们讨论 F# 中另一个结构——管道操作符,它通过类似命令 shell(如 Windows PowerShell ® )管道的通道获取函数的结果,并将结果用作后一个函数的输入。我们来看 图 2 中显示的 F# 代码段。该代码段使用 System.Net 命名空间连接 HTTP 服务器,获取相应的 HTML 并分析结果。

/// Get the contents of the URL via a web request 
let http(url: string) =
let req = System.Net.WebRequest.Create(url)
let resp = req.GetResponse()
let stream = resp.GetResponseStream()
let reader = new System.IO.StreamReader(stream)
let html = reader.ReadToEnd()
resp.Close()
html
let getWords s = String.split [ ' '; '/n'; '/t'; '<'; '>'; '=' ] s
let getStats site =
let url = "http://" + site
let html = http url
let words = html |> getWords
let hrefs = html |> getWords |> List.filter (fun s -> s = "href")
(site,html.Length, words.Length, hrefs.Length)

请 注意 getStats 定义中的 words 标识符。它获取从 URL 返回的 html 值,并对其应用 getWords 函数。我还可以编写定义读取:
let words = getWords html
两 者等同。但是 hrefs 标识符显示了管道操作符的威力,通过管道操作符可以将任意多个应用程序连接起来。此处我获取 words 的结果列表,并将其通过管道传递给 List.filter 函数,该函数使用匿名函数查找单词 href,并在表达式为 true 时将其返回。并且,最重要的是,getStats 调用的结果将是另一个聚合 (string * int *int * int)。要使用 C# 编写,需要的远远不止 15 行代码。
图 2 中的示例还显示出更多 F# 与 .NET Framework 的兼容性,以下代码也表现出这一特性:
open System.Collections.Generic
let capitals = Dictionary<string, string>()
capitals.["Great Britain"] <- "London"
capitals.["France"] <- "Paris"
capitals.ContainsKey("France")
确 实,这个示例除了练习 Dictionary<K,V> 类型外没有什么其他内容,但它显示出在 F# 中如何指定泛型(使用与 C# 相同的尖括号)、如何在 F# 中使用索引(同样与 C# 一样使用方括号),以及如何执行 .NET 方法(使用与 C# 中同样的点和圆括号)。事实上,这里仅有的新内容是使用左箭头操作符为可变值赋值。这一点是必需的,因为 F# 与大多数函数式语言一样,保留等号用于比较,以便保持数学符号含义:如果 x = y,则 x 与 y 的值相等,而不是将 y 的值赋给 x。(真正的数学家们早已对普遍存在或设想过的 x = x + 1 提出异议,甚至偷笑不已。)

 

F# 也能够处理对象
当 然,并不是所有开始使用 .NET 的开发人员都愿意立即接受函数式的概念。事实上,大多数从 C# 或 Visual Basic 转向 F# 的开发人员都需要知道他们在使用这一新语言时可以保留原有的习惯。在某种程度上,这是完全可行的。
例 如,请看 图 3 顶部所示的二维向量的类定义。其中就有一些有趣的概念。首先,请注意其中没有显式构造函数体;第一行中的参数表明用于构造 Vector2D 实例的参数本质上就是构造函数。因此长度标识符,以及 dx 和 dy 标识符将成为 Vector2D 类型的私有元素,而 member 关键字则表明可以通过标准 .NET 属性访问获取的 Vector2D 外部可用成员。本质上,这段 F# 代码声明了您可在 图 3 底部看到的内容(由 Reflector 报告)。

                                                            VECTOR2D IN F#

type Vector2D(dx:float,dy:float) =
let length = sqrt(dx*dx + dy*dy)
member obj.Length = length
member obj.DX = dx
member obj.DY = dy
member obj.Move(dx2,dy2) = Vector2D(dx+dx2,dy+dy2)

VECTOR2D IN C# (REFLECTOR>
[Serializable, CompilationMapping(SourceLevelConstruct.ObjectType)]
public class Vector2D
{
// Fields
internal double _dx@48;
internal double _dy@48;
internal double _length@49;

// Methods
public Vector2D(double dx, double dy)
{
Hello.Vector2D @this = this;
@this._dx@48 = dx;
@this._dy@48 = dy;
double d = (@this._dx@48 * @this._dx@48) +
(@this._dy@48 * @this._dy@48);
@this._length@49 = Math.Sqrt(d);
}

public Hello.Vector2D Move(double dx2, double dy2)
{
return new Hello.Vector2D(this._dx@48 + dx2, this._dy@48 + dy2);
}

// Properties
public double DX
{
get
{
return this._dx@48;
}
}

public double DY
{
get
{
return this._dy@48;
}
}

public double Length
{
get
{
return this._length@49;
}
}
}

请 记住,F# 与大多数函数式语言相似,提倡使用不变的值和状态。当查看 图 3 中的代码时,这一点尤为明显,因为所有属性都为只读属性,并且 Move 成员不会修改现有的 Vector2D,而是从当前 Vector2D 创建新的副本,并在返回副本之前对其应用修改的值。
还 请注意,F# 版本不仅具备整体线程安全性,而且完全可以通过传统的 C# 或 Visual Basic 代码进行访问。这为 F# 入门提供了一种简便方法:使用它定义想要或者需要线程安全和固定不变的业务对象或其他类型。虽然完全可以在 F# 中创建提供常用可变操作组(设置属性及类似操作)的类型,但需要更多的工作,而且需要使用 mutable 关键字才可完成。在当今并发问题成为日常工作主旋律的世界中,这正如许多人所要求的一样——默认固定不变,必需或想要时可变。
在 F# 中创建类型很有趣,但还是可以用 F# 去做那些传统 C# 或 Visual Basic 代码可以做到的工作,如创建简单的 Windows 窗体应用程序并从用户处收集输入,如 图 4 所示。

#light

open System
open System.IO
open System.Windows.Forms
open Printf

let form = new Form(Text="My First F# Form", Visible=true)

let menu = form.Menu <- new MainMenu()
let mnuFile = form.Menu.MenuItems.Add("&File")
let filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*"

let mnuiOpen =
new MenuItem("&Open...",
new EventHandler(fun _ _ ->
let dialog =
new OpenFileDialog(InitialDirectory="c://",
Filter=filter;
FilterIndex=2,
RestoreDirectory=true)
if dialog.ShowDialog() = DialogResult.OK then
match dialog.OpenFile() with
| null -> printf "Could not read the file.../n"
| s ->
let r = new StreamReader(s)
printf "First line is: %s!/n" (r.ReadLine());
s.Close();
),
Shortcut.CtrlO)

mnuFile.MenuItems.Add(mnuiOpen)

[<STAThread>]
do Application.Run(form)

任 何熟悉 Windows 窗体的开发人员都能够立即明白这些代码的含义:创建一个简单的窗体、填充一些属性、填入一个事件处理程序,并告诉应用程序开始运行,直到用户单击右上角的 “关闭”按钮。由于标准元素与 .NET 应用程序相同,所以只需重点关注 F# 语法即可。
Open 语句的操作与 C# 中 using 语句的作用大致相同,本质上都是打开 .NET 命名空间以便在没有正式限制符的情况下使用。Printf 命名空间是 F# 原有的、技术上与 OCaml 模块具有相同名称的端口。F# 不仅具备完整的 .NET Framework 类库,而且还有最简洁的 OCaml 库端口,这使得熟悉该语言的程序员能够象使用 .NET Framework 一样对其运用自如。(致好奇心强的程序员:Printf 位于 FSharp.Core.dll 程序集中。)您完全可以根据个人偏好随时使用 System.Console.WriteLine。
窗 体标识符的创建利用了 F# 命名参数,它等同于实例化对象,然后调用一系列属性集来为这些属性填充值。我在下面的几行中使用相同的方法创建对话框标识符。
mnuiOpen 标识符的定义包含令人感兴趣的结构,该结构对于熟悉 .NET Framework 2.0 匿名委托或 .NET Framework 3.5 中 lambda 表达式的开发人员来说并不陌生。构造与 Open MenuItem 关联的 EventHandler 时,您可以看到使用以下语法定义的匿名函数:
fun _ _ -> ...
类 似于匿名委托,这段代码创建了一个将会在选中菜单项时调用的函数,但语法略有技巧性。
MenuItem 定义中对 EventHandler 的定义是忽略传递给它的两个参数的匿名函数,这两个参数巧妙地对应标准 EventHandler 委托类型中的发送方和事件参数。该函数规定显示新的 OpenFileDialog 并在单击“确定”时检查结果...如下所示:
if dialog.ShowDialog() = DialogResult.OK then
match dialog.OpenFile() with
| null -> printf "Could not read the file.../n"
| s ->
let r = new StreamReader(s) in
printf "First line is: %s!/n" (r.ReadLine());
s.Close();
将 使用模式匹配检查结果,该方法是函数化语言世界中一项强大的功能。模式匹配表面上与 C# 中的 switch/case 在某些地方存在相似之处,但实际上它名副其实地完成模式匹配工作:它将值与各种不同的模式进行比较(这些模式不需要都是常量值),并执行匹配的代码块。因 此,以此处所示的匹配块为例,OpenFile 的结果可以匹配两种可能的值:null 表示无法打开任何文件,或者分配任何非 null 值的 s,该值将随后用作 StreamReader 的构造函数来打开并读取给定文本文件的第一行。
模 式匹配是大多数函数式语言的重要部分,对它做些研究是完全值得的。它的一个最常见的用途是与可辨识联合 (discriminated union) 类型(C# 或 Visual Basic 中枚举类型的不确切说法)配合使用:
// Declaration of the 'Expr' type
type Expr =
| Binary of string * Expr * Expr
| Variable of string
| Constant of int

// Create a value 'v' representing 'x + 10'
let v = Binary("+", Variable "x", Constant 10)
函 数式语言中常用它来创建域特定语言的核心表示,开发人员可以使用它来编写更为复杂和强大的结构。例如,不难想象扩展此语法以创建完全计算式语言,并可简单 地通过为 Expr 类型添加新元素而进一步扩展该语言。这里需要注意的是:使用 * 字符的语法并不表示使用乘法,它是函数式语言中用于指示某类型中包含多个部分的标准方式。
事 实上,函数式语言已经非常普遍地应用于编写面向语言的编程工具(如解释器和编译器),并且 Expr 类型最终将成为语言表达式类型的完整集合。F# 通过其内置的两个工具:fslex 和 fsyacc(专为获得传统语言输入—lex 和 yacc 文件—并将其编译成 F# 代码以便简化操作而设计)使这一切变得更为简单。如果对此感兴趣,可以下载 F# 安装程序深入研究;特别是标准 F# 发行包中的 Parsing 示例将提供非常好的入门基础结构。
可 辨识联合只是模式匹配的优势之一,另一项优势是表达式的执行,如 图 5 所示。位于 eval 定义中的 rec 是必需的,它告诉 F# 编译器在定义主体内迭代调用 eval。如果没有它,F# 将期望出现一个名为 eval 的本地嵌套函数。实际使用时,我使用函数 getVarValue 为变量返回一些预定义的值,getVarValue 将检查 Dictionary,查找变量创建时确定的返回值。

let getVarValue v =
match v with
| "x" -> 25
| "y" -> 12
| _ -> 0

let rec eval x =
match x with
| Binary(op, l, r) ->
let (lv, rv) = (eval l, eval r) in
if (op = "+") then lv + rv
elif (op = "-") then lv - rv
else failwith "E_UNSUPPORTED"
| Variable(var) ->
getVarValue var
| Constant(n) ->
n

do printf "Results = %d/n" (eval v)

当 调用 eval 时,它将得到值 v 并发现该值是一个 Binary 值。这与第一个子表达式匹配,该表达式随后把值 (lv, rv) 绑定到刚检查的 Binary 值左右两侧的计算结果。未命名的值 (lv, rv) 是一个聚合(本质上是代表多个部分的单个值),这一点与关系集或 C 结构相似。
当 首次调用 eval l 时,来自 Binary 实例的 l 恰好是 Variable 类型,因此对 eval 的递归调用匹配该模式匹配块的分支。随后将调用 getVarValue,它会返回硬编码 25,该值最终将绑定到值 lv。对于包含值 10 的常量 r 来说顺序相同,因此它将绑定到 rv。然后执行代码块的剩余部分(if/else-if/else 块),熟悉 C#、Visual Basic 或 C++ 的开发人员可以很容易地读懂该代码块的含义。
这 里需要再次强调的是:每个表达式都将返回一个值,甚至在模式匹配块内部也一样。在本例中,返回值是一个整型值,该值可能是运算得到的值、从变量中检索到的 值或者是常量本身。这一点似乎更容易让习惯于面向对象或过程化编程的开发人员产生微词,因为在 C#、Visual Basic 或 C++ 中,返回值是可选的,并且甚至在指定返回值的情况下仍可以忽略返回值。在类似 F# 的函数式语言中,要忽略返回值需要显式编码表达方式。如果出现这种情况,程序员可以将结果传给名为 ignore 的函数,由它完成适当的操作。

 

异步 F#
目 前为止,我对 F# 语法的介绍采用以下两种方式中的一种:或者使用相对简单的函数式结构,或者使其看起来比较初级且简洁,象是传统面向对象、.NET 兼容语言(C#、Visual Basic 或 C++/CLI)的变体。这种介绍很难推动在企业内采用 F#。
但 是请看一下 图 6 。它可与前面两种形式截然不同。除了多处出现 ! 字符并使用 async 修饰符外,这是一段看起来相对比较直观的代码:加载源图像映像、提取其数据、将数据传递到独立的函数进行加工(旋转、拉伸或其他操作),并将数据写回输出 文件。

let TransformImage pixels i =
// Some kind of graphic manipulation of images

let ProcessImage(i) =
async { use inStream = File.OpenRead(sprintf "source%d.jpg" i)
let! pixels = inStream.ReadAsync(1024*1024)
let pixels' = TransformImage(pixels,i)
use outStream = File.OpenWrite(sprintf "result%d.jpg" i)
do! outStream.WriteAsync(pixels')
do Console.WriteLine "done!" }

let ProcessImages() =
Async.Run (Async.Parallel
[ for i in 1 .. numImages -> ProcessImage(i) ])

较 不明显的是使用 async 修饰符使这段代码进入 F# 所称的异步工作流(与 Windows Workflow Foundation 无关)中,这意味着这些加载/处理/保存步骤的每一步都在 .NET 线程池的平行线程中执行。
为 了使其更简单,看一下 图 7 中的代码。这种特殊的顺序以相对简单且易于理解的方式显示出异步工作流。不用深究细节,我们就可以看出 evals 是一组待执行的函数,通过 Async.Parallel 调用使其中每个函数都在线程池中排队等待执行。当执行时,可以看出实际上 evals 中的函数与 awr 中的函数在不同的线程中(尽管由于 .NET 系统线程池的特性,部分或全部 evals 函数有可能在相同的线程中执行)。

#light
open System.Threading

let printWithThread str =
printfn "[ThreadId = %d] %s" Thread.CurrentThread.ManagedThreadId str

let evals =
let z = 4.0
[ async { do printWithThread "Computing z*z/n"
return z * z };
async { do printWithThread "Computing sin(z)/n"
return (sin z) };
async { do printWithThread "Computing log(z)/n"
return (log z) } ]

let awr =
async { let! vs = Async.Parallel evals
do printWithThread "Computing v1+v2+v3/n"
return (Array.fold_left (fun a b -> a + b) 0.0 vs) }

let R = Async.Run awr
printf "Result = %f/n" R

这 些函数在 .NET 线程池以外执行再次表明 F# 语言对运行时互操作性的支持非常出色。甚至函数式语言中以前专门为特殊实现(如线程)保留的领域也要依赖 .NET Framework 类库,这表明 C# 程序员能够使用 F# 库或模块,就像 F# 开发人员能够使用 C# 库一样。事实上,将来 F# 的功能(如异步任务)能够充分利用新的 .NET Framework 库(例如并行扩展库中的任务处理库)。

 

与 F# 合作
如 果对 F# 还不是十分清楚,那还有许多有关 F# 语言的介绍,这些内容远远超出一篇文章所能容纳的范围;事实上,在新语法和全新的思考方式(函数式与过程式)之间,坦白说对于已经习惯 C# 或 Visual Basic 等面向对象的普通开发人员来说,要掌握 F# 还需要一些时间。幸运的是,F# 与 .NET 系统中的其他语言可完全互操作,这表示您可以利用许多现有的知识和工具帮助您将 F# 变为自己的编程工具。
F# 开发人员可以访问所有的基类库,并且由于 F# 支持一些过程式和面向对象式开发,完全可以考虑不经过编译,使用 F# 交互模型学习 F# 语法和 Windows Presentation Foundation、Windows Communication Foundation 或 Windows Workflow Foundation 的细节。
如 前所述,开发人员可以在 F# 中编写供应用程序代码其他部分使用的业务对象。因为 F# 类型构造生成的类大部分与使用 C# 或 Visual Basic 构造的类相似,所以 NHibernate 这类的持久型库仍可顺利保留 F# 类型,从而使 F# 能够无缝注入其他正在使用的业务应用程序中。
只 需学习一些 F# 的知识就可帮助您更好地理解 C# 和 Visual Basic 未来版本中的许多新特性,因为其中许多想法和概念——包括泛型、迭代器(C# 中的 yield 关键字)和 LINQ——都来源于函数式和 F# 团队所做的研究。无论您怎么看待函数式编程,它都已是既成的事实,并且还将继续发挥功能。

-------------------------------------

(原文链接:http://msdn.microsoft.com/zh-cn/magazine/cc164244.aspx)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: CC2640R2F是一款广受欢迎的低功耗无线芯片,具有强大的性能和灵活的应用能力。下面是关于CC2640R2F的入门介绍。 CC2640R2F是德州仪器公司(Texas Instruments)推出的一款专为低功耗无线通信设计的芯片。它采用了ARM Cortex-M3内核,运行频率高达48MHz,内部集成了256KB的闪存和8KB的SRAM,功能强大。 CC2640R2F支持多种无线通信标准,包括蓝牙低功耗(Bluetooth Low Energy,BLE)和蓝牙5.2。它具有优异的射频性能和低功耗特性,可以实现长达几年的电池寿命。此外,CC2640R2F还具有良好的抗干扰能力和可信任的安全性能,可以满足不同应用场景的需求。 对于初学者来说,了解CC2640R2F的入门方法可以从以下几个方面入手: 1. 学习基础知识:首先,了解CC2640R2F的硬件结构和功能特性。可以查阅相关的技术手册和参考资料,深入了解芯片的主要组成部分和功能模块。 2. 硬件开发环境:为了开始使用CC2640R2F,需要准备相应的硬件开发环境。可以购买开发板或者评估板,或者自行设计底板并搭建相应的开发环境。 3. 软件开发环境:CC2640R2F的软件开发可以使用德州仪器公司提供的开发工具,如Code Composer Studio(CCS)或IAR嵌入式工具链。熟悉使用这些开发工具,可以编写并调试CC2640R2F的应用程序。 4. 学习编程:CC2640R2F的编程可以使用C语言或者基于C语言的工具。学习编程语言的基础知识,并深入了解CC2640R2F的编程接口和开发流程,可以帮助快速入门。 5. 示例和实践:德州仪器公司提供了丰富的示例代码和应用案例,可以帮助初学者更好地理解和应用CC2640R2F。通过参考这些示例,可以逐步掌握CC2640R2F的使用方法和开发技巧。 总之,CC2640R2F是一款功能强大的低功耗无线芯片,适用于各种物联网和物联网应用。初学者可以通过学习基础知识、准备开发环境、学习编程和实践等途径,快速入门并使用CC2640R2F进行开发。 ### 回答2: CC2640R2F是一款蓝牙低能耗无线芯片,具备较高的性能和低功耗的特点,适用于物联网和其他无线通信应用领域。以下是CC2640R2F的入门指南。 首先,要开始使用CC2640R2F芯片,您需要准备以下工具和材料:一个CC2640R2F开发板,JTAG调试器,用于编程的软件(如Code Composer Studio)和USB数据线。 第二步,将CC2640R2F开发板通过USB数据线连接到电脑上,并打开Code Composer Studio软件。在软件中,您可以选择使用现有的示例代码来帮助您进行快速原型开发,或者根据自己的需求进行定制开发。 第三步,使用JTAG调试器将CC2640R2F芯片与计算机连接。通过调试器,您可以在开发板上进行固件的编程和调试。在Code Composer Studio中,您可以选择下载、调试和单步执行程序。 第四步,开始开发您的应用程序。CC2640R2F支持多种通信协议,如蓝牙低能耗、Zigbee和Thread。您可以选择相应的协议,并利用CC2640R2F的特性进行开发。为了更好地了解CC2640R2F的使用和开发,可以参考官方提供的技术文档和开发板用户手册。 第五步,测试和调试您的应用程序。一旦开发完成,您可以将程序下载到CC2640R2F芯片上,并在开发板上进行测试和调试。可以利用开发板上的各种接口和传感器来验证您的应用程序的功能和性能。 最后,当您满意自己的应用程序并通过测试后,您可以考虑将CC2640R2F芯片集成到您的最终产品中。在进行产品化开发时,您需要考虑一些额外的因素,如电源管理、射频设计、外围电路设计等。 综上所述,CC2640R2F是一款功能强大且易于使用的蓝牙低能耗无线芯片,入门操作包括准备工具和材料、连接和配置开发板、开始开发应用程序、测试和调试,最终将芯片集成到您的产品中。通过深入学习和实践,您将能够灵活利用CC2640R2F芯片开发出适用于物联网和其他无线通信应用的创新解决方案。 ### 回答3: CC2640R2F是德州仪器(TI)公司推出的一款低功耗蓝牙(Bluetooth)无线通信芯片,它适用于物联网、智能家居、健康监测和可穿戴设备等领域。对于初学者来说,要入门CC2640R2F,首先需要了解它的主要特点和基本用法。 CC2640R2F采用了TI的BLE-Stack协议栈,支持标准的蓝牙5.2版本,并具有双模功能,即支持BLE和蓝牙经典模式。这意味着它不仅可以与其他BLE设备进行通信,还可以与传统的蓝牙设备兼容。此外,它具有很低的功耗和较长的电池寿命,非常适合低功耗需求的应用。 要入门CC2640R2F,可以首先了解其硬件和开发工具。CC2640R2F芯片提供了丰富的外设接口包括GPIO、SPI、UART和I2C等,可以与其他外部设备进行通信。TI也提供了相关的开发板和开发工具链,如CC2640R2-LAUNCHXL开发板和Code Composer Studio软件,供开发者进行软硬件开发和调试。 接下来,可以学习CC2640R2F的软件编程。TI提供了BLE-Stack软件包,其中包含一些示例代码和应用程序,帮助初学者快速上手。开发者可以使用C编程语言,基于BLE-Stack进行开发,实现蓝牙通信、数据传输和设备控制等功能。 另外,可以学习CC2640R2F的相关文档和参考资料,包括官方文档、用户指南和应用笔记等。通过阅读这些材料,可以更深入地了解CC2640R2F的功能和用法,并掌握开发和调试技巧。 总之,要入门CC2640R2F,需要了解其硬件特性和开发工具,学习软件编程和相关文档。随着不断的学习和实践,可以逐渐掌握CC2640R2F的开发和应用,从而在物联网和蓝牙通信领域取得更好的成果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值