F#简明教程(一)转

  F#是微软.NET开发平台的一门编程语言,其最大的特点是对函数式编程(FP,Functional Programming)的引入;F#对面向对象(OOP)编程的支持也很出色,使用F#语言,开发人员可以自由选择函数式编程或面向对象编程来实现他们 的项目。此外,F#还可以与.NET平台上C#、VB等其他编程语言紧密结合。CPU多核心化和云计算的背景下,函数式编程可以很好的解决多并发运算的问 题(在处理并发问题方面,面向对象编程存在一定程度的固有缺陷,比如类和实例化过程中产生的一些副作用,详细请参考51CTO.com对另一门函数式编程 语言Erlang的视频访谈《因并发而生 因云计算而热:Erlang专家访谈实录》)。微软看到了这个趋势,试图通过专门为函数式编程打造的F#语言提升.NET平台在并发处理、多核多并发方面的能力,进一步提升开发人员的生产力和代码运行效率。

在2009年的TechED上,51CTO.com就F#和函数式编程的问题视频采访了微软MVP赵颉老师,我们可以采访视频了解F#和函数式编程最近的发展,详细请参考《TechED 09视频专访:F#与函数式编程语言》。

F#小背景:看似年轻的F#已经有近10年的历史。最初由微软研究院的Don Syme于2002年立项研发;F#在2005年推出第一个版本,2007年底,微软宣布F#进入产品化阶段。在不断的改进中,F#从C#、Linq和Haskell中吸收了很多优点。

F#编程起步

F#可以运行在.NET Framework 2.0版本以上的平台。如果你的Visual Studio之前没有安装F#,可以从微软F# Developer Center获得(http://msdn.microsoft.com/en-us/fsharp/default.aspx)。

不能免俗,让我们来看看F#的Hello World代码:

  1. #light
  2. System.Console.WriteLine(“This is one hello”)
  3. printfn “This is another hello”

 

将代码保存为hello.fs文件后,我们需要在命令行中通过fsc.exe编译生成一个.NET程序集。在命令行中的编译代码如下:

  1. fsc hello.fs

通过上面的代码,我们就得到了常见的可执行文件(.exe文件),这就是我们F#的起步——hello.exe。

F#小提示:F#是.NET平台上的一个编译型语言,但仍然可以像脚本语言一样运行。可以使用Visual Studio或fsi.exe(在F#安装目录下的bin目录)进行F#脚本的执行。

代码解读

让我们来仔细看看hello.fs文件里的代码

◆程序首先以“#light”开始,在以后的F#之路上,我们会经常看到“#light”;大多数时候,“#light”总是出现在F#程序的开始位置,这是F#轻量级语法的标识;在最新的F#版本中,#light将作为默认选项。

◆“System.Console.WriteLine”调用一个.NET基础类(熟悉C#或VB.NET的朋友会相当熟悉)用来初始化一些必要的功能。

◆“printfn”是F#的一个常用函数,他会将双引号中的参数输出到控制台上显示。

跟其他程序的Hello World一样,这段F#代码简单易懂,看着跟其他语言写就的Hello World还有些相似;但作为函数式编程语言,F#的语法和编程中的思路却有很大的不同。在下周的章节中,我们将深入F#编程,讲解F#的类型系统及编译机制

在上一篇教程《F#与函数式编程概述》中我们了解到F#和函数式编程的一些特点,更多关于F#语言和函数式编程的介绍可以参考51CTO之前对微软MVP赵颉老师的专访《TechED 09视频专访:F#与函数式编程语言》。本节教程我们将学习到F#的一些基础原理,在开始之前,让我们先温习一下我们的Hello World代码:

  1. #light
  2. System.Console.WriteLine(“This is one hello”)
  3. printfn “This is another hello”

F#是函数式和面向对象的混合体。它有时候会看起来与C#或Visual Basic惊人的相似,但却又完全陌生。F#程序以一系列的表达式形式组成,每个表达式可以通过“let”标识符被指定,比如:

  1. let fles = System.IO.DirectoryInfo(@”C:\Users\Chance”).
  2. GetFiles()

在上面的代码中,“fles”被指定了一个值,在这个例子中,是一个文件路径。有意思的是,程序运行中,直到语句在得到右侧的返回值 前,“fles”的实际类型都没有被详细定义。你可能觉得有些别扭,在Java或其他编程语言中,变量fles应该被定义成一种数据类型,string或 是其他什么类型以在内存中可以明确的被编译器区别对待,但这些规则在F#中有些不同。这也导致我们的F#简明教程稍有不同,我们不会像通常的教程那样介绍 F#的基本数据类型,从某种意义上说,F#可以是任意类型或只有一个类型。

F#小提示:F#是一种类型推断语言,它们在编译过程中被推断和确定。如果你在Visual Studio中编写F#,将鼠标指向某个值就会得到它的类型,编译器可以通过函数体或其他方式的定义推断出类型;Visual Studio是开发F#的主要工具,51CTO推荐您阅读Visual Studio 2010中关于F#的资源一文。

类型推断(Type Inference)

我们说数据的类型是被推断出的,因为F#的编译期进程会试图根据变量自身的特点来判断出它的类型并确保这种类型是安全的。尽管F#是强类型语言,但变量的类型声明在类型的判断推理过程中并不是必须的。

类型推断有自身的优点。在使用F#开发一些大型应用时,比如.NET和Java开发者都很熟悉的泛型特性(Generics)便是由类型推断来完 成。注意,F#编译器会视任何没有类型标注的表达式为泛型。例如,下面的函数中,各变量的类型被定义(推断为)泛型,即使程序编写者没有定义任何类型。

  1. let f x =
  2.      let y = g x
  3.      h y
  1. let f (x:’a) : ’b =
  2.      let y:’c = (g:’a->’c) x
  3.      (h:’c->’b) y

F#小提示:在F#中,泛型类型参数是一个以撇号为前缀的字符。比如上面例子中的’b和’c就是最常用的泛型参数。像在.NET中一 样,泛型类型也可使用尖括号语法,比如“Dictionary<’Key,’Value>”。只有一个泛型参数的时候,你有时候会看到它使用 ‘前缀’语法而不是尖括号——最常见的是和F#泛型类‘list’和‘option’一起使用。比如“int list”和“list<int>”表达同一种功能,只是书写方式不同。

F#类型推断机制

F#语言中的大多数类型推断可以遵循以下两条规则。首先,如果一个函数用于产生一个值,编译器将假定该值的类型是函数需求的。第二,如果一个值是一个表达式的必然结果,这个值的类型是这个表达式所决定的。

有些情况下这些简单的规则不够完全,编译器必须需要类型声明。比如,当一个算数运算符被使用,F#会处理的非常谨慎,如果没有程序员的明确代码,不会将一个数值型赋予另一个。这样做是为了确保F#在进行大规模数值计算时,类型推断不会加重编译器的负担。

针对第二条规则的例子在方法过载的情况下发生。比如Write方法在System.Console(.NET中System.Console封装了 基于控制台应用程序的输入、输出和错误流操作)中有18个负载。类型推断可以确定传送给它的类型,但是无法确定另一个方向传送的值的类型。

类型推断不只是简单的符号,它还可以用于程序功能的检测。当你写了一段代码,类型推断功能为这些代码智能的获得了指定的类型,这意味着错误不会被引入程序。这种机制使F#获得动态语言的代码简洁性的同时保证了完全静态的类型系统。

更多关于F#的类型和语法基础请参考:

F#数据类型:Discriminator Union

F#基本语法,模式匹配及List

F#的类型系统和类型推断机制是学习和理解F#语言的基础,掌握了这些有利于我们之后的学习。下周我们将继续F#的学习,一起探究F#的基础语法。

在上一节F#教程中,我们对F#的类型系统和类型推断机制有了一个初步的认识。F#的类型推断原理是学习F#的重要基础。本节课程,我们将在F#类型基础上进一步学习F#的一些基本语法。

“let”表达式是F#语法的核心,可以用作定义函数、序列等多种用途。另外,F#使用空格来标记程序块的开始与结束。

定义值

  1. let x = 2
   

定义函数值

  1. let f a = a + x

定义循环函数

  1. open System.IO
  2. let rec printSubDirFiles dir =
  3.     let fles = Directory.GetFiles dir
  4.     let dirs =   Directory.GetDirectories dir
  5.     printf “%s\n%A\n\n” dir fles
  6.     Array.iter printSubDirFiles dirs

此外,F#还提供传统的循环和迭代等流程控制结构,比如if、for、while。但我们需要注意的是,F#中的“if…then”和 “if…then…else”与传统的面向对象语言有些不同。在F#中,大多数表达式必须含有一个值,并且控制结构“if…then…else”表达式的 两边的值必须是同一类型。注:F#的这种语法约定源自其推断型语言的编译机制,详细请参考上一节教程中关于F#类型推断机制的介绍。

F#中的常用流程控制语句示例

F#常用流程控制语句

与大多数.NET平台上的编程语言相似,F#也提供一些组织代码的机制。事实上,F#提供模块和命名空间两种方式,下面的一些演示将给出C#和VB的命名空间。F#的模块化不只局限与语法范围,还提供模块化的层级标准,例如集合和函数。

F#的基础代码组织:命名空间、类型和模型

  1. namespace MyFSharpProg
  2. open System.Net
  3. type Foo () =
  4.    member x.GetRequest = WebRequest.
  5. Create
  6. module Main = begin
  7. // values and functions here
  8. end

与传统的函数式编程原则相同,多数时候,F#的标识符是不可变的。但F#允许定义和修改使用“mutable”保留字的值,或通过“ref”保留字 改变其前面的引用。mutable的值可以通过左箭头操作(“<-”);ref的值可以通过“:=”操作符制指定。我们可以通过“!”获取ref的 值。下面来看具体示例:

声明/更新可变值

  1. let mutable x = 0
  2. x <- x + 1

 

声明/更新参考值

  1. let x = ref 0
  2. x := !x + 1

 

F#小提示:在习惯了C#或Java等编程语言后,刚刚开始F#编程,阅读F#代码感觉就像乱码一样。因为F# 为了保有函数式编程的一些优秀特质,不得不引入一些如“<-”、“:=”、“!”等奇怪的符号作为操作符或运算符;另外,F#在代码中需要通过一些 推断机制来评判变量的类型,在阅读F#代码时,应对F#的类型系统做到心中有数,所以,多数时候我们看到的是“let”,而不是传统的“int”、 “string”、“float”等。希望大家能充分理解F#的类型系统和类型推断机制,这是F#的重要基础,也是走进函数式编程语言的重要一步。

posted @ 2011-01-14 12:38  董雨  阅读(659)  评论(0编辑  收藏  举报