我的博客

Another Joycode.MVC Powered Blog
随笔 - 7, 评论 - 0, 引用 - 0

导航

每月存档

广告

F#中的管道操作符

原文出处:http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!165.entry

Translated by Sean Wang

在以前的博文中, 我曾经提到管道操作符和类型推断之间的交互. 我想找一个比较自然的例子来说明如何在F#中使用管道操作符, 最终在Wikipedia中找到了. 在Wiki中的这一页描述了Unix系统命令行的管道操作符, 而且提供了一个例子,其功能是根据一个给定的URL,使用Unix管道操作符进行拼写检查. 那么,让我们来用F#写一个小工具. F#中每一步的逻辑和Unix的例子中有些小小的不同, 但它对于了解较长的管道操作符来说仍然是个不错的例子.

open System.IO
open System.Net
open System.Text.RegularExpressions
// fetch a web page
let HttpGet (url: string) =
let req = System.Net.WebRequest.Create(url)
let resp = req.GetResponse()
let stream = resp.GetResponseStream()
let reader = new StreamReader(stream)
let data = reader.ReadToEnd()
    resp.Close()
    data

// Use Word to spellcheck (assumes you have referenced Microsoft.Office.Interop.Word.dll)
let msword = new Microsoft.Office.Interop.Word.ApplicationClass()
let mutable x = System.Reflection.Missing.Value :> System.Object
let Spellcheck text = 
    msword.CheckSpelling(text, &x, &x, &x, &x, &x, &x, &x, &x, &x, &x, &x, &x)
// find all misspelled words on a particular web page, using a pipeline
printfn "misspelled words:"
HttpGet "http://www-static.cc.gatech.edu/classes/cs2360_98_summer/hw1"
|> fun s -> Regex.Replace(s, "[^A-Za-z']", " ")
|> fun s -> Regex.Split(s, " +")
|> Set.of_array 
|> Set.filter (fun word -> not (Spellcheck word))
|> Set.iter (fun word -> printfn "   %s" word)

让我们仔细的检查一下这些管道操作符(最后六行的代码). 一开始, 我们使用HTTpGet 函数从网络上获取页面(一个很明显是我随机选择的页面, 绝对没什么特别含义), 函数返回了一个字符串,里面是网页的内容:

HttpGet http://www-static.cc.gatech.edu/classes/cs2360_98_summer/hw1

接着我们将字符串输入一个函数,它可以将字符串依据空白处分解成一个由单词构成的数组:

|> fun s -> Regex.Split(s, " +")

接着我想将数组按顺序排列并且删除重复的项, 通过创建一个包含这些单词的Set,我们可以很简单地实现这个功能:

|> Set.of_array

然后我们想过滤这个Set, 去掉那些拼写错误的单词:

|> Set.filter (fun word -> not (Spellcheck word))

最后, 我们要打印出Set中每一个剩下的单词(拼写正确的):

|> Set.iter (fun word -> printfn "   %s" word)

这是一个不错的例子, 利用长管道操作符, 阅读起来非常自然.

(管道操作符同样也说明了一条,在定义函数时使用柯里化参数比使用元组参数好的原因. 不过,我将在其他的博文中进一步讨论这方面细节)

当然,我们不用管道操作符也可以实现拼写检查功能. 一个巨大的表达式, 看起来是这样:

(Set.iter (fun word -> printfn "   %s" word)
(Set.filter 
(fun word -> not (Spellcheck word))
(Set.of_array
(Regex.Split(
                Regex.Replace(
                    HttpGet "http://www-static.cc.gatech.edu/classes/cs2360_98_summer/hw1", 
"[^A-Za-z']", 
" "
), " +"
))
)
)
)

哦…这看起来就像你在其他任何一门语言(C#)中写出的巨型表达式一样, 而且它也揭示出了使用巨型表达式的问题: 可读性差. 尤其是表达式是内部求值的, 这意味着”第一件事情的发生(最内层的表达式)”嵌套在表达式中央, 接着它的结果作为参数传递给包裹在它外面的函数, 接着再传递给更外面一层… 带有深层嵌套函数的代码对人来说可读性很差, 因为按照事物发生的顺序去了解它的细节问题,会更加容易. 所以,如果不使用管道操作符,那 还有一个主要的方法, 就是把每一个中间表达式命名:

let page = HttpGet "http://www-static.cc.gatech.edu/classes/cs2360_98_summer/hw1"
let pageWords = Regex.Replace(page, "[^A-Za-z']", " ")
let wordArray = Regex.Split(pageWords, " +")
let wordSet = Set.of_array wordArray
let filteredWordSet = Set.filter (fun word -> not (Spellcheck word)) wordSet
Set.iter (fun word -> printfn "   %s" word) filteredWordSet

这些代码读起来舒服多了. 现在函数按它们执行的顺序出现, 每一行只实现一个功能. 每一个中间结果都被命名, 然后被通过名字引用. 至于他们的命名是好是坏,则取决于你命名是否自然, 我想,可能既有帮助也有阻碍. 我觉得, 只有当你取的名字可读性好,并且能够解释每一步发生了什么,才有必要进行命名.除非读者对Regex类和Set模块有着一定的了解,那么这个例子就会有问题. 因此, 你必须调整自己的代码风格, 要么像上例中使用一大堆”let”, 要么使用管道操作符风格:

HttpGet "http://www-static.cc.gatech.edu/classes/cs2360_98_summer/hw1"
|> fun s -> Regex.Replace(s, "[^A-Za-z']", " ")
|> fun s -> Regex.Split(s, " +")
|> Set.of_array  
|> Set.filter (fun word -> not (Spellcheck word))
|> Set.iter (fun word -> printfn "   %s" word)

当然, 也不是说处处都要这么写. 一般来说, 混合的风格会使可读性更好,我就比较喜欢这样:

let page = HttpGet "http://www-static.cc.gatech.edu/classes/cs2360_98_summer/hw1"
let words = Regex.Replace(page, "[^A-Za-z']", " ")
|> fun s -> Regex.Split(s, " +")
|> Set.of_array
words |> Set.filter (fun word -> not (Spellcheck word))
|> Set.iter (fun word -> printfn "   %s" word)

我之所以喜欢这样的格式, 是因为它是我们的眼睛能够从左边检查代码, 看看主要执行了哪些任务: 我们获取了一个页面, 我们提取了一些单词, 我们要对这些单词做一些处理.

就这样了. 希望现在你对管道操作符”|>” 能有一个更好的了解 – 它如何运作以及你什么时候应该使用它. 这个东西是如此的简单(“x|>f”就等同于”f x”), 却又引起了多少的争议.

posted on 2009-10-21 10:43:47 by franceslam  评论(0) 阅读(1755)

F# 基本语法—类型

原文出处: http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!1077.entry

Translated by Mike

学习F#的时候,有三种主要的关于’类型’知识你需要知道. 第一:你需要知道常用的一些.net类型的名称/别名(比如,”int”意思是System.Int32).第二:你需要知道F#特有类型的名字(比如”unit”),这些类型是什么/做什么/什么意思。最后,你需要知道写这些类的语法(比如:泛型使用一对尖括号,函数使用“->”)。这篇博文将会覆盖上面这些有用的F#知识点。

其实这些知识点我在其他博文中 已经涵盖了(至少提到过);这篇文章主要是强调有关‘类型’的知识,并且提供了很多详细内容的链接。

常用.net类型的F#名称

下面是一个常见.net类型和他们在F#中别名的对照表

System.Object

obj

System.Boolean

bool

System.Int32

int

System.String

string

System.Double

float

System.Char

char

<arrays>

array<'a> or 'a array or 'a[]

System.Collections.Generic.IEnumerable<T>

seq<'a>

System.Exception

exn

表中列出的只是最常见的,其实还有更多类型,要想知道基础类型缩写的完整列表,请参考F#设计文档

F#特有类型

F#有许多特有的类型;其中最常用的几个是:unit,list,ref,tuples,Async,Map和Set。我会依次做一个简单说明。

unit类型只有一个值,写做”()”。它有点儿像“void”,从某种意义上说,如果你只想使用一个函数的副作用,这个函数就需要返回一个“unit”类型。每一函数都接受一个参数并且返回一个结果,所以 你用“unit”来指明函数的参数/返回值你并不感兴趣/没意义。(请看这里

list<’T>类型是F#中非常常用的一个类型。它代表一个不可变得单链表。列表常值被写在一对方括号中(如“[1;2;3]”)。“::”操作符可以合并一个元素到列表的最前端,或者把一个列表分解成第一个元素和剩余的元素(如:“head::rest”)。在列表模块中提供了很多供 列表使用的函数。

option<’T>类型用来表示一个可选值,“Some(x)”或者“None”。更多详情请看这里这里

ref<’T>类型有时候用来创建一个可见值。更多详情请看这里这里.

Tuples 用逗号来分隔(并且通常用小括号括起来),如:“(1,2,3)”,元组的类型名称就是由各个组件类型的名称之间加上’*’组成。举个例子,“(true,42)”的类型就是“bool*int”.更多详情请看这里

Async<’T>类型用来表示异步表达式。像学习更多关于Async的知识,这篇博文是一个不错的切入点。你也可以观看PDC视频(从52:20开始的八分钟时间里展示了Async;整个视频是一个很不错的关于F#的介绍)。

Map<’Key,’Value>和Set<’T>类型仅仅是常见.net“Dictionary<Key,Value>”和“HashSet<T>”类型的一个粗略的不可变版本。

另外一个值得一提的类型就是函数类型(functions)。F#函数类型(如”int->int”)没有‘名字’(不像在C#中那样你需要使用一个像“Func<int,int>”这样的有名称的代理类型)。在后台,F#用FastFunc类来表示函数类型。所以当你看到“FastFunc”(比如:在.net 反射器中浏览F#代码的时候),这个就是它。

F#表达类型的语法

(由于F#是一种类型推断语言,所以你写类型名称的机会相比其他语言会非常少。很多时候大部分与类型名称有关的交互来自于提示栏中推断出来的类型名称。)

上面已经提到过,F#用“->”语法表示函数类型;“A->R”是一个接受A并且返回R的函数(请看这里)。

在F#中,泛型类型参数是一个以撇号为前缀的字符。比如’a和’T就是最常用的泛型参数。像在.net中一样,泛型类型也使用尖括号语法,比如“Dictionary<’Key,’Value>”。只有一个泛型参数的时候,你有时候会看到它使用‘前缀’语法而不是尖括号——最常见的是和F#泛型类‘list’和‘option’一起使用。举个例子:“int list”和“list<int>”是同一个东西,只是书写方式不同(这里提到过)。

所以你可以写成“array<int>”或者“int array”,在F#中最常见的数组类型书写方式是:“int[]”。多维数组类型在方括号中使用逗号,比如整数型二维数组就是“int[,]”,三维数组就是“int[,,]”。

类型声明在F#中看起来是这个样子“e:type”,这里‘e’是一个表达式或者模式。类型声明最常用在函数声明中。举个例子:“let f(x:int):int=x+1”——第一个“:int”是说参数‘x’是一个‘int’型,第二个“:int”是说函数的返回值类型是‘int’。函数中的泛型参数通常是隐式的,比如“let id (x:’a):’a = x”,但也可以是显式的,比如“let id<’a> (x:’a):’a = x”。‘_’字符可以用来表示一个不用指明名称的类型;举个例子,如果‘myList’是一个“list<int>”类型,那么表达式“myList :> seq<_>”将会把myList向上转换成一个“seq<int>”类型,因为‘_’被推断为‘int’。

有很多类型系统中的语法很少被用到,所以在这里很简单的介绍一下。语法 #type 差不多就是 “forall ‘a when a :> type”的意思;这个知识点很少被用到/需要。语法^a 和’a很相似,但是允许使用含有有趣的类型约束的运算符重载(“静态的解决类型变量”,按照 C++中模板的方式);这是一个很级别的知识点(仅被算术家使用)。‘delegate’关键字被用来创建.net代理类型;这个特征只在需要和其他.net语言交互时有用。

posted on 2009-10-21 10:40:17 by franceslam  评论(0) 阅读(1753)

F# 函数类型—函数与元组和柯里化

原文出处: http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!169.entry

Translated by Mike

我本想多写一些关于柯里化的内容,但是我发现柯里化,元组,函数类型,lambda和partial application都是相关的话题。在我给出特别丰富的实例之前我至少先简单介绍一下它们在F#中是如何工作的。所以今天的文章将是关于这些话题在F#中的基础。

元组

元组是一个很好的开始点。一个元组就是两个或更多值(比如:两个,三个,等等)的一个组合。在F#中它们是一些被逗号分开的实体。下面是一些关于元组的例子:

// t2 : int * string
let t2 = (3, "foo")
// t3 : int * string * bool
let t3 = (4, "bar", true)

针对每一个例子,我已经在它们上面的注释中显示给出了类型注释。这里的“t2”是一个包含int和string的二元组。它的类型名称是“int*string”;各元类型在F#中用“*”连接。同样的,“t3”是一个包含一个布尔型的三元组。

元组外面的一对小括号是可选的,也就是说,我也可以这样来写

let t2 = 3, "foo"

然而为了清晰起见,人们通常还是会加上小括号的,而且我也会鼓励你这么写。(我发现就我个人而言,如果没有小括号的话,逗号很容易被忽视。)

unit类型

这里并不存在零元组和一元组,试着举个例子来说明以下原因:

// u : unit
let u = ()
// x : int
let x = (3)

一对空的小括号表示“unit”类型的唯一值。F#中的“unit”类型与C#语言中的“void”最相似。不需要返回值的函数(因为调用它们只是为了得到它们的副作用)就返回一个unit。而且,不出意料的,把单个值用括号括起来并不会影响它的值和它的类型。(同大部分语言中一样,括号的一个主要目的仅仅是为了在你写表达式的时候需要特殊的执行顺序时重载普通操作符的顺序,像在“3*(2+1)”中。所以“(3)”仅仅是“3”的“括号版本”。)

函数类型

既然我们已经对元组类型和unit类型有了一个基本的理解,让我们来关注一下函数类型。看看下面的这段F#代码:

// f : int -> string -> unit
let f x s = 
    printfn "%d %s" x s
// g : int * string -> unit
let g(x,s) = 
    printfn "%d %s" x s
f 3 "foo" // prints 3 "foo"
g(3, "foo") // prints 3 "foo"

函数“f”和“g”相似,但是并不相同。不同之处在于他们是怎样接受参数的。f的参数是柯里化的,然而g的参数时元组。这个区别在调用端是很明显的。调用“柯里化”函数时它的参数是用空白分开的,而调用“tupled”函数时,它的参数是一列用括号括起来并用逗号分开的一些值。我们已经知道一列用小括号括起来用逗号分开的值组成一个元组。所以 你可能猜想你也可以这样写:

g t2        // prints 3 "foo"

你是对的,g的类型“int*string->unit”已经说清楚了。通常,“A->R”已经给出了一个接受A类型参数返回一个R型值得函数的名称。所以g的类型已经告诉我们它接受一个“整形-数组型”二元组返回unit类型。明白了吗?

那么f呢?他的类型是“int->string->unit”。箭头“->”是向右关联的,这就说明这个类型可以被认为是“int->(string->unit)”。所以我暂时定义一个“A”为“int”型,定义一个“R”为“string->unit”型,我们将看到f是一个接受A并返回R的函数——也就是说,f是一个接受“int”型并 返回“接受string型返回unit型的函数”的一个函数。如果我在调用f的时候加了括号就更明显了:

(f 3) "foo" // prints 3 "foo"

函数应用向左关联,这就意味着对f的调用和前面的是一回事。因此我们可以把这段代码解释为:调用带一个参数的函数f,f接受一个int并返回一个新的带一个参数的函数,这个函数接受一个string返回一个unit。为了说的更清楚:

// fp : string -> unit
let fp = f 4
fp "bar" // prints 4 "bar"

这里我只给了f一个参数并且把返回值命名为“fp”。它具有我们所期待的类型——一个接受string型并返回unit型的函数。调用函数时所给的参数比它实际所期待的参数少(生成一个新的函数来接受剩余的参数)的这种技术叫做“partial application”。我们把“fp”叫做“f”的部分应用。它已经接受了一个int了,但是它还在等着接受一个string。这就是“柯里化”形式函数的本质。省略掉后面的参数就很容易的实现了部分应用。

部分应用也可以用在接受元组参数的函数上,不过这样做需要一个显示的lambda。看下面:

// gp : string -> unit
let gp = fun s -> g(4, s)
gp "bar" // prints 4 "bar"

这里我们对g做了同样的操作,我们给了他一个int(值为4)并把结果命名为“gp”。gp还在等待string,这就是说我们我们调用它的时候只需要像例子中那个只给一个string。注意:为了部分应用g,我们必须使用lambda。这就是他在大部分编程语言(那些没有把柯里化语法内置的语言。)中的应用;看一个例子,Dustin Campbell的partial application in C#

尽管有关F#类型系统与函数类型(特别是当调用.net中带有多个参数的方法)具体怎么工作还有许多需要详细描述,我们今天还是覆盖了很多新的领域。今天我想说的重点是:像下面这样思考你就可以得到一个很好的思维模型。

· F#函数类型被写做“A->R”

· 所有的F#函数接受一个参数。但是:

o 参数类型可以是一个元组,意思是你可以有这样的调用“g(x,s)”,并且

o 返回值类型可以是另外一个函数,意思是你可以有这样的调用“f x s”。

通常,在写F#代码时,柯里化方式是优先考虑的。因为柯里化函数在调用端可以避免很多不必要的括号,而且,柯里化函数允许通过省略后面的参数方便的部分应用。你可能已经在一些我以前写的博客中看多关于后面这一点的例子,应为部分应用在管线操作中很常见。

例子

我们来看一个小例子加深一下印象。假设我有一个列表,我想得到一个只含有列表中奇数的新列表。我能想象一下写一个叫做“JustTheOdds”的函数:

let origList = [1; 2; 3; 4; 5]
let newList = JustTheOdds origList
printfn "%A" newList  // prints [1; 3; 5]

If you are aware of the "filter" function from the List module, then writing "JustTheOdds" is easy:

let IsOdd x = 
(x % 2) = 1
// justTheOdds : list<int> -> list<int>
let JustTheOdds l =
    List.filter IsOdd l

然而在实际应用中我并不喜欢写一个这样的函数。“JustTheOdds”并没有增加多少功能,特别是和“List.filter IsOdd”比起来。都是接受一个list<int>并返回一个list<int>的函数。我们来近距离的看一看表达式“List.filter IsOdd”所描述的值得类型。

// List.filter : ('a -> bool) -> list<'a> -> list<'a>
// IsOdd : int -> bool
// (List.filter IsOdd) : list<int> -> list<int>

list.filter是一个柯里化的函数,它的第一个参数是一个接受’a并返回bool型的一个函数。(旁白:在F#中,泛型参数被命名为一个标识符前面加一个撇号,在C#中我们通常用“T”来代替。在任何情况下,list<’a>和C#中的list<T>一样——filter是一个接受一个一个类型参数的泛型函数。)它的结果是一个新的函数,这个函数接受一个list并且返回一个list。 IsOdd符合list.filter的第一个参数(泛型参数’a被绑定到“int”型上)。结果,当我们应用list.filter到IsOdd上,我们得到一个接受list<int>参数并返回一个list<int>的结果的函数。这正是我们所需要的。所以,我更喜欢把代码写成这样:

let origList = [1; 2; 3; 4; 5]
let newList = origList |> List.filter IsOdd
printfn "%A" newList  // prints [1; 3; 5]

这里我们把原始的列表传送给一个列表转换函数——这个函数是部分应用list.filter到IsOdd上的结果。因为这个例子很简单,我可以只写成这样:

let newList = List.filter IsOdd origList

这就是说,这个例子并不需要部分实现和管道传输。但是如果我们对列表作一系列复杂的转换时,管道传输模式和部分实现就有了用武之地。事实上,我们几经在先前关于管道传输的博客中看到这些,在那里我们可以看到在Set模块中有许多函数的部分应用。

总的来说,柯里化函数的部分应用是一个很方便的技术,在实际应用中我发现这个技术最常用在管道传输中。

(Teaser:我已经有写一个使用部分实现的更详细例子的主意,例子中使用部分实现的方法更有趣,我希望能在不久的将来把它写出来。)

posted on 2009-10-21 10:39:40 by franceslam  评论(0) 阅读(1685)

F# ——面向语言编程

原文出处: http://blogs.msdn.com/chrsmith/archive/2008/05/30/language-oriented-programming-in-f.aspx

翻译:Mike Feng & Sean Wang

上周二我在 .NET Developers Association 作了一次关于F# 中的面向语言编程的演讲。

你可以在这里找到视频。下面是演讲的文字版,不幸的是转换的不是很成功。在继续进行之前,先对这篇文章如此冗长表示歉意。

什么是面向语言编程

就让我们从面向语言编程(LOP)的概念说起吧,这是一个很模糊的词汇,像元编程。我不想把它注释的太具体,而是会把它定义为一个较宽泛的术语并提供大量例子。

要理解LOP首先要理解领域特定语言(DSL)的概念。一门DSL是一种被设计来解决很窄领域内特定问题的编程语言(相对那些多用途语言来说,如C#和F#,它们是用来解决各领域任何问题的)。DSL的其中一个例子就是Excel。写一个公式,累加两个单元格的内容“=A1+B1”,无需定义任何数据结构,函数,会话例程等等。Excel语言自身含有对于表格单元格的概念, 所以你无需描述‘A1’代表什么。而另一方面,在C#中,你不能写‘A1’而要写些诸如“MasterSheet.GetCell(new CellObject(Row = “A”, Column = 1));”之类的。

DSL最大的优点就是,解决相同的问题时,它的代码比多用途语言要简单。有了DSL,你通常为了表达自己想法所搭的框架,现在不在需要了,因为对于这一问题领域的所有关键概念都已被包含在语言自身内部。

尽管如此,DSL也有两个缺点。首先,DSL强迫你学习新的语言。第二,某些(不熟悉的)人需要了解这门语言,还要为它创建编译器。对于小型的问题,这个缺点不算什么。但是,如果你想用DSL向你的整个公司描述业务逻辑,你就会看出DSL的缺陷了。不过,如果你对创建DSL有兴趣的话,可以看这里:Toolkit

那么什么是LOP呢?给你一个绝好的例子,让我来介绍一下Google Code上一个叫FsUnit的工程。这是一个用于在F#中写单元测试的类库,但是不是采用‘Assert.IsTrue(x)’的写法,而是:

// Equality

1 |> should (equal 1)

// Checking existence in a collection

[| "item1" |] |> should (contain "item1")

[| "item1" |] |> should (notContain "item2")

// Size of a collection

personList |> should (have 4 "people")Some text matches a regular expression:

// RegEx patterns

"test infected" |> should (matchThePattern "inf")

// Other primitives

true |> should (be True)

false |> should (notBe True)

"" |> should (be Empty)

"a string" |> should (notBe Empty)

null |> should (be Null)

anObj |> should (notBe Null)

FsUnit 允许你用不同语言的词汇和概念去写F#代码。(在这个例子中,是英语)。基于这个例子,我对LOP给出如下定义:LOP是一种编程风格,它试图产生看起来像是领域特定语言的代码,但这些代码却仍然适用于多用途编程语言。

这篇文章的剩余部分,将会给出关于LOP在F#中使用的一些例子,并且说明如何使用简单的代码去更好地描述手边的问题。我的例子主要针对三个主题:

· 抽象表达。F#的特性允许你在F#中表达领域特定的概念,而不需要进行新一层的抽象。

· 具体表达。F#的特性允许你用别的语言描述问题,然后载入到F#中。

· 计算表达。最后我将介绍F#的另一些特性,它们使你能够用(F#)代码处理其他语言中的概念,而不需要借助于更特殊的第三方语言。

第一部分 – 抽象表达

使用现代面向对象语言是,编程者面临的一个问题就是,如何在语言中表达概念。C#只含有能表达对象及其行为的功能,这就使它难以准确的描述抽象理念。一个含有“喵”和“咕噜”方法的猫类可能还有意义。可是你如何在C#中表达“快乐”的概念?用“使什么高兴”方法吗?

对于抽象表达,我要说的是F#的一种能力,它能使你的代码在表达概念时尽可能自然。

类型缩写

我要说的第一个特性是类型缩写。在F#中你可以创建另一个类型的别名,它在编译时会被替换为原始核心类型。也就是说类型缩写只存在于设计时期。使用类型缩写,你可以对于问题领域的概念写代码,而无需引入客户类型。举例来说,我可以给‘int’起名叫CustomerID。现在,如果我要写一个接受CustomerID类型的函数,我就能知道它实际上需要什么,而不同于一个接受‘int’类型的方法,那样我需要猜测‘int’代表什么。

type CustomerID = int
let alice = 98123
let bob   = 78435 : CustomerID
// With the type annotation the value 'customer' appears as to
// have type 'CustomerID' but at compile time that is replaced
// with 'int'. You can pass both alice and bob to function
// getCustomerOrders.
let getCustomerOrders (customer : CustomerID) =
    printfn "%d has ordered 5 items."customer

特别是在你处理负责的泛型类时,类型缩写很有帮助。试想一下Dictionary<string, string>。它有些作用,但仅仅从类型签名上,你无法得知键和对应的值分别是什么。但是通过类型缩写,在调用时,你会更容易理解。

open System.Collections.Generic

type TeamCityLookup = Dictionary<string, string>

// A TeamCityLoop is a better description for the type than Dictionary<string, string>

let teamLookup = new TeamCityLookup()

teamLookup.Add("Mariners", "Seattle")

teamLookup.Add("Reds", "Cincinati")

teamLookup.Add("Dodgers", "Los Angeles")

teamLookup.["Mariners"]

可识别联合

想像一下下面一段C#代码,它代表纸牌花色的概念。

enum CardSuit

{

Club,

Spade,

Diamond,

Heart

}

代码看起来简单清晰,但实际上它带来了很多问题,因为仅仅通过枚举还不足以表达一套互斥值的概念。用C#很容易写出如下代码,破坏了一张牌只能拥有一种花色的理念。

CardSuit invalid1 = CardSuit.Club | CardSuit.Heart;

CardSuit invalid2 = (CardSuit) (-1);

为了写出一张牌的概念,你的代码必须写的稍稍再复杂一些。你可以看看这里:chapter 21 of Effective Java。而在F#中,你可以非常方便地表达这种理念。

type Suit =

| Diamonds

| Hearts

| Spades

| Clubs

// An instance of 'Suit' can only have one of four possible values

let printSuitName suit =

match suit with

| Diamonds -> printfn "Suit is a Diamond"

| Hearts -> printfn "Suit is a Heart"

| Spades -> printfn "Suit is a Spade"

| Clubs -> printfn "Suit is a Club"

此外,在F#中可识别联合同样可以附加上数据,使它变得比枚举更有用,而定义起来同样便捷。

// Discriminated Unions can hold data too!

type Card =

| ValueCard of int * Suit // Value 2 - 10 and Suit

| Jack of Suit

| Queen of Suit

| King of Suit

| Ace of Suit

| Joker

// Simple syntax for defining instances of Disc Unions

let myPokerHand =

[

ValueCard(2, Hearts)

ValueCard(5, Spades)

Joker

ValueCard(4, Clubs)

Ace(Clubs)

]

可选值类型

不好意思,又拿C#举反例,不过这还是有一个.NET使人困惑的例子,这原本是可以避免的。试想一下下面的代码,它的作用是:如果你有宠物,我就会得到一个你宠物的实例,并打印它的名字。

Pet yourPet = you.GetPet();

if (yourPet != null)

Console.WriteLine("You have pet named " + yourPet.Name);

代码看起来够简单了。但是在现实世界中哪里存在null呢?在问题特定的这个领域就存在吗?如果我问你你是否有一只宠物袋熊,你会回答‘null‘吗?不,null是编程语言中人为创造出来的产品,用来表示什么东西不存在,或未初始化的值。但在LOP中,我们试图不用任何辅助措施来表达这种概念。而F#就有这样一种可选值类型,可以用一种更自然的方式表达“没有”的概念。

type Pet =

| GoldFish

| Dog

| Cat

// Takes a 'Person' type and returns an option, of their pet type

let getPetType person =

match person with

| Me -> Some(Dog)

| SomebodyElse -> Some(GoldFish)

| You -> None

如果你获得了一个宠物 Some(..)的返回值,说明你有(宠物)。如果你没有,则获得None。可选值类型的另一个很大的好处是它可以准确的传递意愿(不会有歧义)。如果你在C#中调用‘GetPet’方法,当你没有宠物时,结果就说不清了。这个方法是抛一个异常还是仅仅返回null?在F#中,使用可选表达式,意味着你没有宠物时就返回None。

模式匹配

模式匹配是能够使我们的代码表达更清晰的另一种方式。在C#中你可以使用switch表达式,但仅仅对常量奏效。在F#中,模式匹配可以做的,远不止比较“值”和“常量”这么简单。比如,它们还可以对比结构体和数据。下面我们试着匹配一下链表的长度:

// Structure of data

let shortList = ['a'; 'b'; 'c']

let printListLength list =

match list with

| [] -> printfn "List is empty"

| [_] -> printfn "List has 1 element"

| [_;_] -> printfn "List has 2 elements"

| [_;_;_] -> printfn "List has 3 elements"

| _ -> printfn "List too long"

另外,模式匹配还可以捕获变量的一部分进行匹配。

// Match contants and capture variables

let sayHello (first, last) =

match (first, last) with

// Match constants

| "Bill", "Gates"

| "Steve", "Balmer"

-> printfn "Steve and Bill, wazzzup!"

// Match first against constant, capture second

| "Chris", last

-> printfn "Hello Chris. Your last name is %s" last

// Capture both values

| first, last

-> printfn "Hello %s %s" first last

演示

现在我们有了所有用F#表达理念的基本概念,我们有能力将一个真实世界中的数学问题,用(F#)语言表达出来。

// This Discriminated Union is sufficient to express any four-function

// mathematical expression.

type Expr =

| Num of int

| Add of Expr * Expr

| Subtract of Expr * Expr

| Multiply of Expr * Expr

| Divide of Expr * Expr

// This simple pattern match is all we need to evaluate those

// expressions.

let rec evaluate expr =

match expr with

| Num(x) -> x

| Add(lhs, rhs) -> (evaluate lhs) + (evaluate rhs)

| Subtract(lhs, rhs) -> (evaluate lhs) - (evaluate rhs)

| Multiply(lhs, rhs) -> (evaluate lhs) * (evaluate rhs)

| Divide(lhs, rhs) -> (evaluate lhs) / (evaluate rhs)

// 10 * 10 - 25 / 5

let sampleExpr =

Subtract(

Multiply(

Num(10),

Num(10)),

Divide(

Num(25),

Num(5)))

let result = evaluate sampleExpr

在这个简单的例子中,我们能够仅仅用模式匹配和可识别联合,去对一个四则运算的数学表达式求值。要用C#写出相应代码,可能会麻烦得多,因为你必须增加很多额外组成部分来表达这些概念。

第二部分 具体表达

具体表达是指,在其他语言中具体的描述你的问题,再把它读入F#程序。通过使用领域特定语言和F#的协同工作,你可以用特殊的术语描述你的问题,并用F#作必要的处理。

fslex fsyacc

一个处理关于其他语言具体表达的最简单的方式,就是建立一个解析器,然后像编译器那样载入它。Lex和Yacc三十年以来一直是产生解析器方面的标准工具。FsLex和FsYacc是Lex和Yacc用于产生F#解析器的。这里我不会讲的太深入,不过如果你感兴趣可以看我以前的博文

Lex和Yacc是两种DSL,用于描述编译器的解析器和分析器,或者说是一个,用于将原数据分解为一系列符号,然后将这些符号转化为抽象语法树的工具。举例来说,这里有一段Lex代码,将字符串“10 * 8 + 5.0”转化为符号[INT32(10); ASTER; INT32(8); PLUS; FLOAT(5.0)]

rule tokenize = parse

| whitespace { tokenize lexbuf }

| newline { tokenize lexbuf }

// Operators

| "+" { PLUS }

| "-" { MINUS }

| "*" { ASTER }

| "/" { SLASH }

// Numberic constants

| ['-']?digit+ { INT32 (Int32.Parse(lexeme lexbuf)) }

| ['-']?digit+('.'digit+)?(['e''E']digit+)? { FLOAT (Double.Parse(lexeme lexbuf)) }

| eof { EOF }

对应的Yacc解析器代码看起来像下面这样,它将符号与语法产品相匹配并产生AST节点。

Prog:

| Expr EOF { $1 }

Expr:

| Expr PLUS Term { Plus($1, $3) }

| Expr MINUS Term { Minus($1, $3) }

| Term { Term($1) }

Term:

| Term ASTER Factor { Times($1, $3) }

| Term SLASH Factor { Divide($1, $3) }

| Factor { Factor($1) }

Factor:

| FLOAT { Float($1) }

| INT32 { Integer($1) }

如果这些内容,还没有让你理解Lex和Yacc,不必担心。(你只要明白)重点是,如果你需要,就会有可用的工具,可以使你写出能够解析任何其他语言的F#程序。 因此,如果你想写出对于所有数学等式(“1^5 + cos(PI)”)的解析器,你能。如果你要写出结构化记录文件的解析器,你能。如果你要写出F#代码的解析器,使你能在F#程序中熟练操控它,你能。有了fslex和fsyacc,你能够写出任何领域特定语言的解析器,并且在F#程序中读入它的抽象表达。

活动模式

我要谈的下一个特性,允许你将其他语言的具体表达转化成F#,叫做活动模式。(我曾在前面的博文中谈过。)理解活动模式最简单的方式,就是把它想象成一种将数据从一种表达方式转化成另一种的方法,典型的就是通过模式匹配。

活动模式由以下三种组成:单条件,多条件,以及部分。

单条件活动模式

单条件活动模式接受一种数据类型的输入,然后转化成另一种。在下面的例子中,我们将字符串转化成整形。注意‘匹配语句’末尾处活动模式的结果。

// SingleCase Active Patterns

// Covnert a string to an int

let (|IntValue|) input = Int32.Parse(input)

// Given a string print its integer representation.

let printValue (str : string) =

match str with

| IntValue 0 -> printfn "str is zero"

| IntValue 1 -> printfn "str is one"

| IntValue 2 -> printfn "str is two"

// Variable capture of AP output

| IntValue x -> printfn "str is %d" x

多条件活动模式

多条件活动模式将输入转化成几种输出类型之一,或是将输入分隔成几个部分. 举例来说,我们可以将整形转化成几类: 奇数,偶数,或是零。

// Multi-Case Active Patterns

let (|Even|Odd|Zero|) x =

if x = 0 then Zero

elif x % 2 = 0 then Even

else Odd

// Takes an int and prints its status

let printStatus x =

match x with

| Zero -> printfn "%d is zero" x

| Even -> printfn "%d is even" x

| Odd -> printfn "%d is odd " x

部分活动模式

部分活动模式就像单条件活动模式,但它们并不是都能被成功的匹配上.在上一个例子中我们把字符串转化成整数,但是一个字符串并不是总能被转化成整数。比如“foo”。部分活动模式使用可选值类型来表示数据是否被成功转换。这里我们将把字符串转化为整数或者是浮点型数字。

// Partial Active Pattern
let (|ToInt|_|) str = 
    let (parsed, result) = Int32.TryParse(str)
    if parsed then Some(result)
    else           None
 
let (|ToFloat|_|) str = 
    let (parsed, result) = Single.TryParse(str)
    if parsed then Some(result)
    else           None
 
// Takes a string and prints whether it is an int or float
let parseValue str =
    match str with
    | ToInt   x -> printfn "str is an int with value %d" x
    | ToFloat x -> printfn "str is a float with value %f" x
    | _         -> printfn "str is neither an int nor a float"

演示

这里有一个例子,是关于如何利用活动模式的,它的作用是将其他语言的具体表达转化成F#。例子来自于一份由Don Syme, Margetson 和 Gregory Neverov所作的研究报告演示,这个报告是用于介绍活动模式的概念的。代码比较复杂,但是我会点破其中玄机:仅仅用一行代码,我们就可以根据xml文档我们可以提取出所有有用的信息,那就是属性和嵌套节点。

// Concrete language

let xmlDoc =

let temp = new System.Xml.XmlDocument()

let superHerosXmlDoc =

"<?xml version=\"1.0\" encoding=\"utf-8\"?>

<Scene>

<Sphere r='3' x='4' y='3' z='0' />

<Intersect>

<Sphere r='2' x='1' y='0' z='0'/>

<Intersect>

<Sphere r='2' x='4' y='0' z='0'/>

<Cube d='1' x='6' y='7' z='8' />

<Sphere r='2' x='-3' y='0' z='0'/>

</Intersect>

<Cube d='2' x='-2' y='1' z='0'/>

</Intersect>

</Scene>

"

temp.LoadXml(superHerosXmlDoc)

temp

// Abstract representation

type GeometricScene =

| Cube of float * float * float * float

| Sphere of float * float * float * float

| Intersect of GeometricScene list

// Mach an XML element

let (|Elem|_|) name (inp: #XmlNode) =

if inp.Name = name then Some(inp)

else None

// Get the attributes of an element

let (|Attributes|) (inp: #XmlNode) = inp.Attributes

// Match a specific attribute

let (|Attr|) attrName (inp: XmlAttributeCollection) =

match inp.GetNamedItem(attrName) with

| null -> failwith (attrName + " not found")

| attr -> attr.Value

// Convert a string to a float

let (|Float|) s = Float.of_string s

// Parses a vector out of an attribute collection

let (|Vector|) inp =

match inp with

| (Attr "x" (Float x) &

Attr "y" (Float y) &

Attr "z" (Float z))

-> (x,y,z)

// Parses a GeometricScene from an XML node

let rec (|ShapeElem|_|) inp =

match inp with

// By using nested Active Patterns we can parse all attributes on one line!

// (Attributes (Attr "r" (Float r))) gets the Attributes of the node, then gets

// the attribute "r", then finally converts its string value into a float.

| Elem "Sphere" (Attributes (Attr "r" (Float r) & Vector (x,y,z)))

-> Some (Sphere (r,x,y,z))

| Elem "Intersect" (ShapeElems(objs))

-> Some (Intersect objs)

// This is what the cde would look like without nested Active Patterns

| Elem "Cube" xmlElement

-> match xmlElement with

| Attributes xmlElementsAttributes

-> match xmlElementsAttributes with

| Attr "d" dAttrib & Vector (x, y, z)

-> match dAttrib with

| Float d -> Some(Cube(d, x, y, z))

// Did not recognize XmlNode as Shape element

| _ -> None

虽然活动模式看起来易读性不是很好,但却能够使你很轻松的转化数据。

第三部分-计算表达

This is the last part of Language Oriented Programming and definitely the most complicated.这是面向语言编程的最后一部分也肯定是最复杂的。 The theme of this essay has been that two languages are better than one; that using a domain-specific description the your problem makes the coding easier.这篇文章的主题是两种语言优于一种语言,使用特定领域描述您的问题使编码更容易。而使用使用两种语言来表示您的问题,其唯一优点是你不必使用三种语言。

设想一下,一个普通的基于数据的表格应用程序。设设想 You have a database, CustomerInfo, and you have your application written in C#.你有一个数据库,客户信息,并且你的程序用C#代码编写。 With any luck you're using LOP in your programming and so you can represent your customer entities naturally, but you run into a problem as soon as you try to interface with your database.幸运的话您使用面向语言编程,因此可以很自然的表示你的客户实体,但是当你需要和你的数据库交互的时候你就会遇到问题。 Namely, having your app talk to the database requires you using another language – SQL.也就是说,要想使你的应用程序和数据库交互,你需要使用另外一门语言——SQL。

With Visual Studio 2008 this problem was solved for a few specific scenarios with Linq , but in F# you can solve this problem even more generally.Visual Studio 2008中这个问题已经有好几种Linq解决方案 ,但在F#中你可以更方便的解决这个问题。

关于LOP 最后一个方面中我将讨论的是你在哪里写F#代码和怎么执行那些代码所表示的计算。换句话说换句话说,你写了一些有趣的F#代码,然后用独特的方式执行那些代码所表示的计算或者是把它转化成另外一种语言。

The F# language features Quotations and Workflows will undoubtedly be the subject of numerous articles, blog posts, and maybe even books in the future. F#的语言点引用和工作流将会毫无疑问的成为很多文章,博客,将来有可能甚至是书籍的主题。所以如果我做一些疯狂的努力来解释这些语言点做些什么,你不要害怕。

Quotations 引用

Writing .NET code is great and all, but the implicit restriction is that your code is executing on computer running the .NET framework.写.NET代码是很棒的,但隐性的限制是,您的代码必须执行在运行.net framwork的电脑上。 That doesn't sound like a big requirement, but think for a moment on how limiting that is.这听起来不像一个大的要求,但仔细想一想它的限制性。 如果你想写出,在GPU上执行的代码,你将不得不采用If you wanted to write code that executed on your GPU you would have to resort to some other language .其他一些语言 。 Or let's say you were multiplying two sparce matricies and wanted to optimize out all the zero-multiplications, leading to a more efficent code.或者说,您试图使用两个空间矩阵相乘,想优化所有零乘法,从而使更代码有效。 You can't.你做不到。 Because all the code you write in .NET must execute on the .NET platform… or at least it had to before F# came along.因为所有你用.NET写的代码必须在.net平台上执行。 或者至少在F#出现之前必须这样。

In F# the Quotations feature allows you to get access to the compiler's representation of a block of code, enabling you to process that code as you wish. F#中的引用允许你使用编译器对一段代码块的表示,使您能够随便处理这些代码。 For example, you could write a Quotation-to-GPU converter and write programs in F# which could execute on your graphics card .例如,你可以写一个引用到GPU的转换器并且用F#编写可以在显卡上运行的程序 。 Or, given a function that operates on a sequence of data, convert those operation into SQL code and automatically query the server.或者,给出一个函数操作一系列数据,把这些操作转换成SQL代码,并自动查询服务器。 (A la DLinq.) (按DLinq的方式 。 )

我不会讨论代码执行的细节,但我会指出关键的概念。 First, the funky “<@@ … @@>” code starts a quotation.首先,有趣的“<@@...@@>” 代码开始了一个引用。 Anything inbetween the <@@ and @@> is what is 'quoted'.任何在<@@和@@>之间的东西都是 ‘被引用的’。 So the result of <@@ x @@> is the compiler's representation of x.<@@...@@>的结果就是编译器对x的表示。第二,一旦你有一些引用的数据,你将能够使用活动模式把编译器的表示转换成更有用的东西。In the simplest example, we will take the quotation of a constant value and use an Active Pattern to convert the compiler's representation of that into the value itself.在最简单的例子,我们将使用常量的引用,并使用一个活动模式把编译器的表示转化成它本身的值。

#light
 
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Typed
open Microsoft.FSharp.Quotations.Raw
 
// The compiler represents the code “1” as an Int32 literal.
let quot1 = <@@ 1 @@>
 
let printQuotation x =
    match x with
    | Int32 v  -> printfn "The quoted code is an int with value %d" v
    | String v -> printfn "The quoted code is a string with value %s" v
    | _        -> printfn "I don't know what x is..."

通过使用活动模式匹配更多类型的表达式,你可以转向更大更复杂的表达式(上例是比较简单的表达式)。

You can then move onto bigger, more complicated expressions by matching more types of expressions using Active Patterns.Here we break down an entire function.  (The [<ReflectedDefinition>] attribute is required for the function to be used inside of a quotation.)这里,我们分析一个完整的函数。 (引用内部的函数必须使用[ <ReflectedDefinition> ]属性。 )

// Functions
[<ReflectedDefinition>]
let checkTemp temp =
    if   temp < 60 then printfn "Too cold"
    elif temp > 80 then printfn "Too hot"
    else                printfn "Just right"
 
let rec printQuotation2 expr =
    match expr with
    | ResolvedTopDefnUse(_,body) 
        -> printfn "The expr is a Top Definition Use..."
           printQuotation2 body
 
    | Lambda(_, body) 
        -> printfn "The expr is a Lambda..."
           printQuotation2 body
           
    | Cond(_, body, nextCond)
        -> printfn "The expr is a Conditional..."
           printQuotation2 nextCond
           
    | App(info, body) 
        -> printfn "The expr is a function application..."
   
    | _ -> printfn "I don't know what the expr is"
 
printQuotation2 <@@ checkTemp @@>

Here is an example from下面是一个来自 Expert F# (Apress, 2007) which uses Quotations to compute the error range for floating point calculations. Export F #( Apress , 2007 )的例子,它采用引用计算浮点运算的误差范围。 Given an inexact number such as p = 3.141 +/- 0.001, repeated operations on p will result in a compounded error.鉴于这样的一个不确切的数字为P = 3.141 + / - 0.001 ,反复对P操作将导致复杂的误差。 Such as p + p = 6.282 +/- 0.002.比如P + P=6.282 + / - 0.002 。Given the quotation of a function, the code will walk the compiler's interpretation of the code and calculate an error estimate.把函数加上引用的功能,代码就可以通过编译器对代码的解释并计算误差范围。

// Example from Expert F# by Don Syme, Adam Granicz, and Antonio Cisternino
// Pg. 251
 
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Typed
open Microsoft.FSharp.Quotations.Raw
 
type Error = Err of float
 
// Estimate the error for a given expression, t.
// env is a map of identifiers to their values. E.g., "pi" -> 3.14159
let rec errorEstimateAux t (env : Map<_,_>) =
 
    match t with
    // If the quoted expression matches the function application of
    // +, -, *, or / calculate the left and right hand sides and
    // compute the resulting error.
    | GenericTopDefnApp <@@ (+) @@> (tyargs,[xt;yt]) ->
        let x, Err(xerr) = errorEstimateAux xt env
        let y, Err(yerr) = errorEstimateAux yt env
        (x + y, Err(xerr + yerr))
 
    | GenericTopDefnApp <@@ (-) @@> (tyargs,[xt;yt]) ->
        let x, Err(xerr) = errorEstimateAux xt env
        let y, Err(yerr) = errorEstimateAux yt env
        (x - y, Err(xerr + yerr))
 
    | GenericTopDefnApp <@@ ( * ) @@> (tyargs,[xt;yt]) ->
        let x, Err(xerr) = errorEstimateAux xt env
        let y, Err(yerr) = errorEstimateAux yt env
        (x * y, Err(xerr * abs(x) + yerr * abs(y) + xerr * yerr))
 
    | GenericTopDefnApp <@@ ( / ) @@> (tyargs,[xt;yt]) ->
        let x, Err(xerr) = errorEstimateAux xt env
        let y, Err(yerr) = errorEstimateAux yt env
        (x / y, Err(xerr * abs(x) + abs(1.0 / y) / yerr + xerr / yerr))
 
    | GenericTopDefnApp <@@ abs @@> (tyargs,[xt]) ->
        let x,Err(xerr) = errorEstimateAux xt env
        (abs(x), Err(xerr))
 
    // If the quoted expression introduced a new value. E.g.,
    // let e = 2.71828
    | Let((var,vet), bodyt) ->
        let varv, verr = errorEstimateAux vet env
        errorEstimateAux bodyt (env.Add(var.Name, (varv, verr)))
 
    | App(ResolvedTopDefnUse(info,Lambda(v,body)),arg) ->
        errorEstimateAux  (MkLet((v,arg),body)) env
 
    | Var(x) -> env.[x]
    | Double(n) -> (n,Err(0.0))
 
    | _ -> failwithf "unrecognized term: %A" t
 
let rec errorEstimateRaw (t : Expr) =
    match t with
    | Lambda(x,t) ->
        (fun xv -> errorEstimateAux t (Map.of_seq [(x.Name,xv)]))
    | ResolvedTopDefnUse(info,body) ->
        errorEstimateRaw body
    | _ -> failwithf "unrecognized term: %A - expected a lambda" t
 
let rec errorEstimate (t : Expr<float -> float>) = errorEstimateRaw t.Raw
 
// ----------------------------
 
[<ReflectedDefinition>]
let poly x = x+2.0*x+3.0/(x*x)
 
errorEstimate <@ poly @> (3.0, Err(0.1))
// Evaluates to: (9.333333333, Err 0.5821493625)
 
errorEstimate <@ poly @> (30271.3, Err(0.0001))
// Evaluates to: (90813.9, Err 3.02723)

Work flows 工作流

Workflows are the most exciting language feature in F#, and represent perhaps the most powerful way to apply LOP in your code.工作流是F#中最令人兴奋的语言功能 ,也许代表着在代码中应用LOP的最强大的方法。 But rather than telling you what they are I'll build up to it.但是,我要建立它而不是告诉你他们是什么。

看看看看下面这段代码,它被叫做序列表达。 It produces a seq of the first 10 integers.它产生一个前10个整数的seq。 The second example is a more complex sequence expression, which uses recursion to walk every file under a given directory.第二个例子是一个更复杂的序列表达,它使用递归遍历某一特定目录下的所有文件。 Note the use of recursion requires the 'yield!'注意:使用递归需要‘yield!’keyword.关键字。

// Sequence Expressions
 
// Numbers one through ten
let numbers = seq { for i in 1 .. 10 do
                        yield i }
 
// All files under a given directory (notice the use of recursion)
open System.IO
 
let rec allFiles dir = 
    seq {   for file in Directory.GetFiles(dir) do
                yield file
            for subdir in Directory.GetDirectories dir do
                yield! (allFiles subdir) }
            
allFiles @"C:\Windows\System32\"

Just to show that you can do powerful things with Sequence Expressions, here is some code to compute all prime numbers under 1,000.只是为了表明你可以使用序列表达式做强大的事情,下面是这些代码,作用是找出小于1000的所有质数。 (The Sieve of Eratosthenes in F#.) (F#中的Sieve of Eratosthenes。)

// Complex Sequence Expression
let primesUnder1K = 
    seq { 
        // First prime 
        yield 2 
 
        let knownComposites = ref (Set.empty)
        
        // Loop through all odd numbers; evens can't be prime 
        for i in 3 .. 2 .. int 1000 do 
            
            // Check if its in our list, if not, its prime 
            let found = (!knownComposites).Contains(i) 
            if not found then 
                yield i 
 
            // Add all multiples of i to our sieve, starting 
            // at i and irecementing by i. 
            do for j in i .. i .. int 1000 do 
                knownComposites := (!knownComposites).Add(j)
    } 
 
Seq.take 20 primesUnder1K
 
        检查一遍,序列表达式产生seq对象。并使用一个看似F #语言的有限子集。 Simple enough.很简单。 

Sequence Expressions however are just a specialization of a concept known as Computation Expression or Workflow.序列表达式是计算表达或工作流的一个特例。 The Computation Expression was everything between the curly braces { and }.计算表达式是在大括号{和}之间的所有东西 。 The 'seq' in front was the workflow builder which I'll come back to in a moment.前面的‘seq’是工作流程生成器,我呆会儿会接着讲。

So why are Computation Expressions important?那么,为什么计算表达式重要? 因为在F#中你能够:定义计算表达式如何通过生成器对象执行 。 In the previous examples the 'seq' builder takes the Computation Expression and produces a sequence of values.在前面的例子,‘seq’生成器接受一个计算表达式并生成一系列值。But you can implement far more interesting builders.但是你可以实现更为有趣的生成器。 You could for example write a builder that logged every action that was taken, in order to better diagnose a failure in the code.例如,您可以写一个生成器用来记录每一个发生地事件,用来更好地诊断代码中的故障。 Or even transfer the computation to a different machine and execute the code in the cloud.甚至可以把计算转移到别的机器中,在“云里雾里”执行代码。 Computation Expressions / Workflows are a powerful new concept that open up a lot of possibilities for powerful language oriented concepts.计算表达式/工作流是一种强大的新概念,开拓了很多强大的面向语言的概念的可能性。

工作流在异步编程中得到了The most practical application of workflows is in asynchronous programming.最实际的应用。 Normally if you want concurrent code you need to write it with a bunch of callbacks, manage thread states, deal with synclocks, and other unpleasantries.通常如果你想写一段并行代码你需要写一大堆回调,管理线程状态,处理synclocks ,和其他不愉快的事情 。 But using F# Asynchronous Workflows, all you need to do is write your code in a Computation Express and pass that computational representation of your code to the Async Workflow object.但是,使用F #异步工作流,所有您需要做的是把你的代码写在计算表达式中,并把你的计算表达传递给一个Async工作流对象。它将会为你的代码执行同步操作。 没错,使用F#,你不用试就可以写出同步代码。

Here's an example of an async workflow in F# from Expert F# .下面是Expert F#(它是一个比我的博客更好的F#教材)中F#异步工作流的一个例子。

// Example from Expert F# by Don Syme, Adam Granicz, and Antonio Cisternino
// Pg. 366
 
open System.Net
open System.IO
open Microsoft.FSharp.Control.CommonExtensions
 
let museums = ["MOMA",           "http://moma.org/";
               "British Museum", "http://www.thebritishmuseum.ac.uk/";
               "Prado",          "http://museoprado.mcu.es";
               "SAM",            "http://www.seattleartmuseum.org/"]
 
// Fetch the museum website and print info to the console asynchronously
let fetchAsync (name, url:string) =
    async { do printfn "Creating request for %s..." name
            let req  = WebRequest.Create(url)
 
            let! resp  = req.GetResponseAsync()
 
            do printfn "Getting response stream for %s..." name
            let stream = resp.GetResponseStream()
 
            do printfn "Reading response for %s..." name
            let reader = new StreamReader(stream)
            let! html = reader.ReadToEndAsync()
 
            do printfn "Read %d characters for %s..." html.Length name }
 
for (name, url) in museums do
    Async.Spawn (fetchAsync(name,url))

That's it.就是这样。 Seriously.认真对待。 The code will loop through all the museums specified, and asynchronously download the HTML of their homepages.代码将遍历所有指定的博物馆,并异步下载他们主页的HTML。 All the work you needed to was write it inside of a Computation Expression and pass it to an 'async' builder.所有你需要做得工作就是把它写在一个计算表达中,并将其交给一个‘async’生成器。 Async.Spawn will take the result and execute it asynchronously. Async.Spawn将接受它的返回值并异步的执行它。

Conclusion 结论

I've shown that language features in F# enable Language Oriented Programming and provide all the power you need write succinct code that maps directly to your problem domain.我已经表明这个F#的语言功能使其能够面向语言编程,并提供所有您需要的力量为您的问题域提供简洁的代码。不需要多余的代码。不 I fully expect that Language Oriented Programming becomes the driving force for F# adoption as developers discover the true expressiveness of the language and leverage these facilities.我完全期望面向语言程序设计成为采用F#语言开发者发现语言丰富表达力的驱动力,并充分利用这些特征。

*Actually, in a poor attempt at humor you've been Rick Rolled . *实际上,缺少幽默感的你一定被Rick Rolled

posted on 2009-10-21 10:34:01 by franceslam  评论(0) 阅读(1991)

F#基础语法 – 类, 接口和成员

原文地址: http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!894.entry

Traslated by Sean Wang

今天的博文涵盖了F#关于编写类,接口和成员的语法。和前面的博文合在一起, 几乎涵盖了这门语言接近90%最常用的内容。为了使本文尽可能的简单,我可能故意在整体的准确性上犯一点儿小错.

定义F#类型

有许多方法可以在F#中定义新类型和类型名, 而且大部分牵涉到带有”type”关键字的声明.今天我只关注类和接口; 在今后的博文中我将会进一步描述其他类型的语法(记录, 可识别联合, 枚举, 类型缩写, 测量单位, 结构体, 委托, 异常和模块).

类(暂不涉及接口或继承)

这里有一个关于如何定义一个类的例子, 它唯一的目的是为了说明和演示所有在定义类时需要用到的语法:

type public (*1*) MyClass<'a> public (*2*) (x, y) (*3*) as this (*4*) =
static let PI = 3.14
static do printfn "static constructor"
let mutable z = x + y
do  printfn "%s" (this.ToString()) (*5*)
        printfn "more constructor effects"
internal new (a) = MyClass(a,a)
static member StaticProp = PI
static member StaticMethod a = a + 1
member internal self.Prop1 = x
member self.Prop2 with get() = z
and set(a) = z <- a
member self.Method(a,b) = x + y + z + a + b

接下来我会讨论其中每一个部分, 注释中的数字有助于我们在后面的讨论中指出其中某个特定的部分.

一个类的定义通常是这样开头:

type SomeClass(constructor-args) = ...

但是在定义类时,等号前面还可以有很多可选的内容. 回到我们的主例上来:

type public (*1*) MyClass<'a> public (*2*) (x, y) (*3*) as this (*4*) =

(*1*)前面的’public’表示这个类的可访问性; 新类型默认为public, 在类名称后面,如果这个类是泛型的, 你必须以尖括号指明泛型形参(MyClass<'a>是一个带有一个泛型形参的类; 泛型形参在这个例子里并没有用到,只是为了演示这种语法才这么写). 接下来一段定义了所谓的”隐式构造函数”(下面将会有进一步的描述). (*2*)前面的’public’表示隐式构造函数的可访问性(默认为public), 而(*3*)前面括号中的内容是隐式构造函数的实参. (*4*)前面的”as this”是对当前对象命名的另一种方式(你可以用任何你喜欢的标识符, 它的作用域包括类中所有非静态的部分); 你唯一需要用它的时候,就是在隐式构造函数中使用”this”时(比如在主例中(*5*)标识的那行).

从概念上说,类体分为两个部分,一个是 ‘let’ 和’do’(作为构造函数的一部分运行), 另一个是’member’. 两个部分都可以有静态和实例(非静态)部分; 默认为非静态. ‘let’前面绑定的名称从语法上说属于类体( 因此它们总是属于类私有的). 非静态的’let’和’do’运行时作为隐式构造函数的组成部分,而静态的’let’和’do’则构成类型的静态构造函数. 换句话说, 这些代码

static let PI = 3.14
static do printfn "static constructor"

当MyClass这个类第一次被访问时就会运行,而这些代码:

let mutable z = x + y
do  printfn "%s" (this.ToString()) (*5*)
        printfn "more constructor effects"

则在你创建MyClass实例的时候运行. (和其他.NET语言一样,很少会定义静态构造函数.)

成员是类用来提供公开接口功能的,主要分为两大类: 属性和方法. 没有实参的成员是读属性的方法(Getter访问器); 而有实参的成员是写属性的方法(Setter访问器). 看下面这个例子:

static member StaticProp = PI 
static member StaticMethod a = a + 1
member internal self.Prop1 = x 
member self.Prop2 with get() = z 
and set(a) = z <- a 
member self.Method(a,b) = x + y + z + a + b

成员可以是静态,也可以是非静态的(默认为非静态). 非静态(实例)成员必须声明一个自身标识符. 就像类声明中的”as this”一样,你可以定义为任何你喜欢的标识符.这个标识符在这个成员体中表示当前对象(在这个例子中我选了”self”). 成员默认为公开的(尽管如此,”Prop1”演示的是你可以加一个可选的可访问性说明,不是默认的). 如果你想定义一个属性的Setter访问器,就像Getter访问器一样,你可以用”Prop2”的语法.

定义一个重载的构造函数而不是隐式的,可以用这样的语法:

internal new (a) = MyClass(a,a)

访问修饰符是可选的( 默认为公开, 就像其他成员一样). 构造函数体必须调用另一个构造函数( 最终在最底层调用的是隐式构造函数). 从实用的角度说,这意味着你的隐式构造函数必须初始化所有内容, 而且因此, 几乎要接受任何重载构造函数的大部分参数.

为简略起见,我忽略了许多关于成员的特性,包括命名, 可选参数, 重载, 事件和字段. 如果想进一步了解这些特性的细节,可以读一下语言规格说明书, 或者是在博文评论里问问题.

接口

在F#中定义和使用接口是非常直截了当的.一个接口就是一个只定义了抽象成员或是继承了其他接口的类型.这里有两个例子:

type IFooable =
abstract member Foo : int -> int
type IQuxable =
abstract member Qux : unit -> string
interface IFooable

“IFooable”是一个包含有方法”Foo”的接口,而”Foo”这个方法接受一个int参数并且返回一个int.”IQuxable”是一个继承自”IFooable”的接口,并且增加了另一个”Qux”方法.

如果要使一个类实现接口,只要在类体最后加上'interface...with'声明,声明每一个你想实现的接口就可以了:

type FooQux() =
member this.SomeMethod() = printfn "hi"
interface IQuxable with
member this.Foo x = x + 1
member this.Qux() = "qux!"

请注意,在F#中, 类实现接口始终是显式的, 这就意味着调用接口的方法时,必须使用显式的向上类型转换. 举例:

let fooQux = new FooQux()
fooQux.SomeMethod()
let x = (fooQux :> IFooable).Foo 42 // must upcast to call "Foo"

抽象类和虚方法

这里有一个抽象类的例子:

[<AbstractClass>]
type SomeBase() =
member this.ConcreteMethod y = y + 1
abstract member AbstractMethod : int -> int
abstract member VirtualMethod : int -> int
default this.VirtualMethod x = x + 1

一个抽象类除了包含一些(继承它的)子类必须实现的抽象成员(在抽象类中没有实现),和一个普通的类没有区别.在C#中虚方法被描述为有一个默认的实现的抽象方法.一个抽象类必须标注上"AbstractClass"属性.

继承

对于继承,F#中有如下3个关键字: 'inherit', 'override', 和 'base':

type SomeDerived() =
inherit SomeBase()
override this.AbstractMethod z = z + 1
override this.VirtualMethod x =
1 + base.VirtualMethod x
member this.OtherMethod() = ()

‘inherit’ 语句为当前类指明了一个基类, 并且调用了基类构造函数作为当前类隐式构造函数的一部分.(也就是说,在上面这个例子中, “SomeDerived”的无参构造函数调用了”SomeBase”的无参构造函数.) 那些没有实现的抽象成员,在派生出的子类中必须被重写(或者子类也被标注为抽象的). 带有默认实现的抽象方法可以被重写; 调用一个继承下来的方法,可以用”base”关键字.

其他

为了确保博文尽量简短,我有意的忽略了很多细节(但还是涵盖了大部分常用语法).在忽略的内容中有几点值得注意:

· 你可以用’class’和’end’标记框住一个类.同样,也可以用’interface’和’end’框住一个接口.但F#通常可以在定义类时推断它的种类,所以这些标记可以省略.

· 一般来说,空白是挺重要的;虽然我列举出来的方法体大多是在同一行里面,但更多情况下,你必须在’=’后面另起一行,并且将声明下面的方法体向内缩进,就像"SomeDerived"中的"VirtualMethod"一样.

· 我在第一部分中,只讨论了访问修饰符(比如’public’,’internal’)和属性(而不是方法); 你可以举一反三, 推知如何定义例如internal virtual的属性.

posted on 2009-10-21 10:32:20 by franceslam  评论(0) 阅读(1606)

F# 基础语法—关键字和结构

原文出处: http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!887.entry

Translated by Mike

我已经写过很多关于F#的博文,但至今为止还没有描述过F#语言的基本语法.所以今天我将尽力弥补这个缺憾,描述一些在F# 代码中常见的一些关键字和语法结构.这篇博文不会覆盖所有的F# 语法,但是差不多能覆盖75%左右的常见语法.为了使本文尽可能的简单,我可能故意在整体的准确性上犯一点儿小错.

#light

大部分的F#文件以”#light”开头

(译者注:在当前的Dev10版本中已经把”#light”设置为默认打开,所以当前的F#文件不需要再显示的调用”#light”)

#light

这个是用来打开”轻量级语法(lightweight syntax)”选项的.我在这里不会讨论和” 轻量级语法”对应的选项是什么,因为人们总是使用”轻量级语法”这个选项.而且在下一个F#版本中”” 轻量级语法”将成为默认选项(译者注:当前的F#版本已经这样做了). 现在还是要确保”#light”总是出现在F#代码文件的开头.

Comments

共有两种类型的注释,看下面的代码

// a one-line comment

(* a multi-line
   comment (* these can nest *) *)

“open” --打开一个命名空间

“open”关键字是用来打开一个命名空间或者模块.

// must fully qualify the name System.Console
System.Console.WriteLine("Hello, world!")

// after opening the namespace, don't need to
open System

Console.WriteLine("Hello, world!")

“let” – 定义函数和值

“let” 关键字用来定义函数和值.这里是使用”let”定义值的一些例子:

let x = 42 // immutable, x is always 42
let mutable y = 0 // mutable, can re-assign y's value with <- operator
let z : string = null // ": string" is type annotation to declare type

你很少会用到类型说明(F#是一种类型推断语言),但是在最后一个例子当中,类型推断还是很有必要的(如果不使用类型说明,’z’将被推断为”obj”(System.Object)类型;这里的类型说明会告诉编译器我们需要’z’是一个”string”类型).

下面是一些使用’let’定义函数的一些例子:

let F x = x + 1
let G x y = x + y
let G2 (x:float) (y:float) : float = x + y
let H(x,y) = x + y
let rec Kaboom x = Kaboom (x+1)

“F”是带一个参数的函数.”G”和”G2”有两个柯里化的参数(后来”G2”中为参数指定的类型说明和为函数返回值指定的类型说明,展示了这种语法),但是”H”使用的是元组(tuple)参数;想知道更多关于元组和柯里化的信息,你一定得读一读这篇博文. 另外,‘let rec’关键字可以定义一个递归函数.

轻量级语法(lightweight syntax)使空格符/缩进显得更有意义,而且缩进是界定函数(和其他结构体)范围最常用的方法.另外,函数可以定义在任何地方.下面这段代码可以说明以上观点:

let Area diameter = // define a function
// everything indented under here is the body of "Area"
let pi = 3.14 // define a value inside the function
let Radius d =  // define another function inside here
// this is the body of the "Radius" function
        d / 2.0
let r = Radius diameter
    pi * r * r

// use the function
let answer = Area 5.0

“fun” -- lambda 表达式在这里是”fun”

“fun”关键字是用来定义lambda(匿名函数)的.语法是”fun 参数 -> 函数体”,而且优先规则往往会强制你把所有的定义放在一对小括号内,请看下面这个例子:

let nums = [1; 2; 3; 4; 5]
let odds = List.filter (fun x -> x%2 = 1) nums
printfn "odds = %A" odds // odds = [1; 3; 5]

在这个例子中,请注意’%’是一个求模操作符,用在这里是为了确定数字’x’是不是奇数, List.filter是对一个列表应用了断言的函数(返回值为布尔型的函数),它返回的是一个通过断言函数返回值为真的元素的 一个列表.

“|>” 管道操作符

一个使用非常频繁的内置操作符就是:pipe. “x |> f” 就是”f x”.因此,上面的例子更习惯于写成下面这个样子:

let nums = [1; 2; 3; 4; 5]
let odds = nums |> List.filter (fun x -> x%2 = 1)
printfn "odds = %A" odds // odds = [1; 3; 5]

其实在这个小例子中使用管道操作符并没有什么实质性的好处, 但是这个操作符经常用来在一系列具有转化性功能函数中”传递”数据.更详细的说明请看这里.

“match” 模式匹配

模式匹配是一个非常强大的语言特征,在很多环境下都可以使用它,但是他最常使用的地方是在’match’表达式中.

match expr with
| pat_1 -> body_1
...
| pat_n -> body_n

这个表达式会测试每一个模式,第一个被匹配上的模式后面的方法体会得到执行.最常见的模式包括简单的代数数据类型,比如可识别联合(Discriminate Union), 特别是针对一个”列表”或者一个”可选值”的匹配;关于可识别联合的描述和怎样针对可识别联合的匹配请看这篇文章的前半部分.(我最后大概会写至少两篇完整的关于模式匹配的文章,不过上面的一段和链接文章中的描述对你来说已经足够了,因为在这你只需要对语言有一个快速的了解.).

条件和循环

虽然在F#中使用的比在其他语言中用的少(一般会用模式匹配和递归替代),F#中还是有if-then-else, while循环,for循环. 由于F#是一门函数式语言,所以这些都属于带返回值的表达式.

下面是if-then-else语法的通式

if cond1 then

expr1
elif cond2 then

expr2
else

expr3

‘elif’和’else’部分是可选的,而且你想使用多少个’elif’(else if)都可以.所有的exprn都必须是同一个类型,这也是整个表达式返回值的类型.如果’else’部分被省略了,exprn必须是’unit’类型(它是”void”的近亲).

While的语法是:

while cond do
expr

整个while表达式总是返回”unit”.在F#语言中并没有像”break”或者”continue”这样的东西.

for pat in expr do
bodyexpr

在这里,expr是一个你可以遍历的东西(比如: Ienumerable<T>, F#中的seq<’a>),pat是任何一个模式(但是最常见的是一个新的标识符名称),bodyexpr在遍历的过程中针对每一个元素执行一遍.举个例子:

for x in someArray do
    printfn "%d" x

打印出”someArray”中的所有整数. 和’while’一样,整个’for’表达式返回’unit’类型.

“new” – 用”new”来创建对象

你可以像在C#中一样使用”new”来创建一个对象.

let x = new System.Uri("http://hello.world/")

然而,在F#中’new’关键字通常是一个可选项.

Literals(该词在翻译上存在争议,具体可参阅这里,本文中为避免误解,不翻译该词)

在F#语言中有很多种Literal,其中最常见的是下面这些:

let b : bool = true // or false
let i : int = 42
let s : string = "hi"
let x : float = 3.14 // "3." same as "3.0"
let ai : int array = [| 1; 2; 3 |] // "int array"=="array<int>"
let lf : float list = [ 1.1; 2.2; 3.3 ] // "float list"=="list<float>"

(这里所有的类型说明都不是必要的,不过他们对我的讲解很有帮助.)布尔,整数,字符串Literal会像你期待的那样正常工作.”浮点型”相当于System.Double, 它是一种包含小数点的数字常量.数组常量用[|括号|]来书写,而列表常量用[括号]来书写;他们都使用分号(或者另起一行)来区分集合中的元素.这里只展示了最常用的一些类型.我将在新的博文中详细的讨论更多F#内置类型.

Exception constructs

F#有一种”try-catch-finally”结构,并且使用C#中的IDisposable.它的基础语法是:

try
expr
with
| pat_i -> body_i
try
expr
finally
cleanup
use ident = expr

更多描述请看这里.

还有什么没有描述到的?

除了 类型(定义新的类型,类,成员等;包括描述不常见的内置类型)和pervasives(内置操作符和函数),这篇简介几乎囊括了95%的F#常用语法结构. 类型和pervasives我打算在我将来的博文中讲解.

posted on 2009-10-21 10:31:34 by franceslam  评论(0) 阅读(1701)

C# 代码哪些部分看起来像F#(第一部分:表达式和语句)

原文出处:http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!725.entry

Translated By Sean Wang

如果你有C #代码并且打算直接翻译成F # , 最终生成的F#代码可能不太符合语法习惯或者不太好。然而,如果你对一门语言(F#)来说是个新手,有时了解一下如何在不懂语法的情况下将一门众所周知的语言(C#)翻译出来是有益处的,不过希望这不要阻止你在(学习新语言方面)取得进步。因此,今天的博文主要采取以下形式:“这里有些C #代码,我怎么写成F # ” 。它的目的是用作参考或休闲阅读,在其中发现一些很少使用或不太知名的F #语法/运行/功能。请不要将此博文用来学习语言,一定要使用那些有益的F#教程,或是样本上的网页,这篇博文只是用来弥补不足。
今天的博文,只包括一些方法内部的代码,所以你不会在这儿找到任何有关声明类和命名空间的内容。取而代之的,我将涵盖以下的C #内容 :
类型转换
操作符
表达式
声明
并且指出如何将这些C #语句翻译成相应的F # 。在此过程中我会加入一些注释,说明什么是好的,不好的,合乎语法的,等等,但我不会涉及太多细节。如果我说的不够通俗,或者是有什么没说清楚,尽管把问题发表在下面的评论栏里。
不再进一步使用ADO !
类型转换
根据源类型和目的类型,类型转换“操作符”(即: “ (类型名)表达式” )在C #中可以代表各种不同的内容。我即将实现4种最常见的类型转换:
// C#
char c = 'a';
int x = (int)c;         // numerical conversion
object o = (object)c;   // boxing conversion
    Dog d = new Dog();
    Animal a = (Animal)d;   // upcast
    d = (Dog)a;             // downcast (may fail at runtime)
以下是相应的F #代码,跟着的是一些注释。
// F#
let c = 'a'
let x = int c        // numerical conversion
let o = box c        // boxing conversion
let d = new Dog()
let a = d :> Animal  // upcast
let d2 = a :?> Dog   // downcast (may fail at runtime)
数值转换在F #通过Library函数实现。例如,函数“int”将字符型(或float或unit或任何)转换成int 。反过来说,这些目的类型也有类似的功能,例如函数“char”可以将传给它的实参转换成char类型。你可以在这里找到更多一些关于这些函数的内容:Library Reference。请注意, “枚举”函数能够(将实参)转换枚举类型;具体枚举类型推断,有时需要类型说明,比如
let y : MyEnumType = enum 0 // F#: y is 0-value of MyEnumType
装箱是将一种原始数据转换为一个对象。由F#函数 “box”实现 。事实上, “box” ,将任何类型向上转换为System.Object (在F #中用缩写名称“obj” ) 。
向上转换 :如果您想向上转换类/接口等级,使用的F #语法“表达式:>类型“ 。如果编译成功,这个转换在运行时也会成功。 (注: 在F# 1.9.6.2中 ,表达式优先级有时有些奇怪,所以有时您可能需要在外面加括号,如“ (表达式:>类型) “ ,以使代码解析正确。 )这个操作符很少会用到。
向下转换:如果您想向下转换一类/接口等级,使用的F #语法“表达式:?>类型“ 。问号在这个里是为了提示操作可能会失败,就像在C #中,可能会得到一个InvalidCastException 。你很少会使用此操作符,在C #中通常选择“is”和“as” ,看下面这些表达式在F#中是如何表达的。
操作符
许多算术(如+ , * )和条件(如& & , | | )操作符在F#和C #里是一样的 。至于不同的地方,最常见的如下: ( 1 )逻辑非:在C #中写成“ ! ” ,而在F #写成“not” ; ( 2 )相等判断,在C #中是“ == ” 而在F#中只是“ = ” ;和( 3 )不等,在C #中有“ ! = ”而F #中就是"<>". (至于逻辑非,注意在F#中, “ ! ”已经有一个和引用类型有关的含义,这是继承自OCaml的。 )其他有些通用的地方,如位逻辑运算符在C#中通常是一个字符(如“ | ”和"&"),而在F #中是三个字符("|||"和"&&&");你在使用如“Flag”枚举时会需要这些操作符。
大多数F#操作符被重载了,在Visual Studio中,像下面这样指向代码时,会有类型推断,不要误解了:
let f x y = x + y  // hover mouse over f, says int -> int -> int

这并不意味着“ + ”只适用于ints ;您可以使用“ + ”来添加两个浮点型,或两个字符型,甚至两个字符串。(至于这一使重载究竟是如何工作的,这里面牵涉到”内联”和”^a”类型。对这里面的细节问题暂时不要研究太深,这会让你学的更愉快些;当对类型没有指定时,语言/工具提示一般选择“整数”作为默认类型,以便隐藏内部的复杂机制。 (”如何重载常用操作符,诸如’+’”,另一个更好的“解决方案”,就是要利用类类型,但是,无论是CLR还是F#都没有提供充分的类型系统去处理类类型. ) )
在C #中,操作符('=')一样是用于初始化和破坏性赋值:
int i = 3;    // initialization
i = 4;        // destructive assignment

在F # 中,这些都是独立的操作符( ' = '和'<-'),只有可变变量接受赋值:
let i = 3 // initialization
    i <- 4 // does not compile, i is immutable
let mutable j = 3
    j <- 4 // destructive assignment

表达式
大多数表达式涉及结构对象,方法和属性,看起来在C #和F #中一样 :
new Dog()  // constructor
s.StartsWith("h", StringComparison.Ordinal)  // method call
s.Length   // property
而一个显著的区别,就是从两种语言的语法上说,数组及其他类型的索引各不相同。在C #中:
var dict = new Dictionary<string, int>();
    dict["foo"] = 42;
    Console.WriteLine(dict["foo"]);
F#中方括号前要加点:
let dict = new Dictionary<string,int>()
    dict.["foo"] <- 42
    printfn "%d" dict.["foo"]

C#中有着大量的表示兰姆达/委托的不同语法,举一个例子:
    Func<int, int, int> f = (x, y) => x + y;  // C# lambda;

F #大致对应如下 :
let f = (fun x y -> x + y) // F# lambda

在表达兰姆达/函数/委托方面,还有很多有趣的区别,我今天就不深入讨论这其中的细节了。
在C#和F#中都有”typeof”操作符返回System.Type,但C#中用圆括号,但F#中用尖括号:
typeof(int)  // C# typeof

typeof<int>  // F# typeof

另一个区别涉及泛型; 在C #中,您可以省略类型说明获得一般性的定义:
Console.WriteLine(typeof(List<int>).IsGenericTypeDefinition); // false
Console.WriteLine(typeof(List<>).IsGenericTypeDefinition);    // true而F #有一个单独的操作符,就是所谓的的“typedefof ”, 获得未实例化的泛型:
printfn "%A" (typeof<List<int>>.IsGenericTypeDefinition) // false
printfn "%A" (typedefof<List<_>>.IsGenericTypeDefinition) // true

C #中有“is”和“as”操作符可以作类型测试。 F #对此采用一个特定的模式。所以这段C #代码:
if (animal is Dog)
    {
        Dog dog = animal as Dog;
// ...
    }
else if (animal is Cat)
    {
        Cat cat = animal as Cat;
// ...
    }

改成F #代码应该是:
match animal with
| :? Dog as dog -> // ...
| :? Cat as cat -> // ...

其中":? type”是一种类型测试,如果那个类型测试成功了,”as 标识符” 就将当前值命名,名字就是as后面的标识符。 (另外说一句: F#中 “else if”可简称为“ elif ” 。 )
C #中有条件表达式,三元运算符“ ? : ”:
    condition ? trueVal : falseVal
F #也有相同的操作符,但它的名字是if-then-else:
if condition then trueVal else falseVal

(请注意,”if”在F#中用的比C#中要少一些,在F #中 ,许多条件表达式都是这样通过模式匹配,而不是if-then-else。 )
C #中有一个操作符”default”, 返回给定类型的零初始化值:
default(int)
它的用处是有限的;最常见的是,你可以在泛型中使用default(T)。 F #在库函数中也有类似的构造:
     Unchecked.defaultof <int>
这些构造在C#和F#中使用的都很少。
语句
本节说明如何将C#语句翻译成F#.F#本身没有语句;一切都是表达式, 对一个方法体求值就意味着对它的表达式求值.表现形式。在F#中,表达式里最像语句的就是”()”的,这个值属于”unit”类型,类似于C#中的void。
C #中有三种循环结构:
// C# loops
while (condition)
    {
        SomeCode();
    }
foreach (var e in someEnumerable)
    {
        SomeCode();
    }
for (int i = 0; i < 10; ++i)
    {
        SomeCode();
    }

F#只有”While”和”Foreach”(在F#中拼写为”for”); 一个C#”for”循环通常被模拟为一个”范围”:
// F# loops
while condition do
        SomeCode()
for e in someEnumerable do // foreach
        SomeCode()
for i in 0..9 do // compiles like a C# for loop
        SomeCode()
F #没有类似”break”或”continue”这样的语句,必须用控制流或布尔变量去模拟这种结构。 “While” 循环在F#中式很少见的,因为这必须在条件中包含可变变量, 而F#通常避免可变(变量等),而是通过递归实现简单的循环.即使是for(each) 循环也很少出现在F#中, 因为有时F#更倾向于这种形式:

someEnumerable |> Seq.iter (fun e -> SomeCode())

(特别是Seq.iter的实参很短的时候)
C #中有一个switch语句。它看起来像这样:
switch (x)
    {
case 1:
            SomeCode();
break;
default:
            SomeCode();
break;
    }

在F #中,可以通过模式匹配更简洁的表示C#的许多语句,switch只是其中一种:
match x with
| 1 -> SomeCode()
| _ -> SomeCode() // _ is a 'catch all' default
C #中有一个“返回”关键字,退出目前的函数。F#中没有相应的构造。请注意, “返回”关键字在F#中是计算表达式语法的一部分(简称“工作流” ,又名“ monads ” ) ,和C#中的”返回”没有任何关系。和C#中的”break”一样 , 在F#中 您需要使用控制流结构模拟“发挥”以实现提前退出的功能。 (虽然我偶尔希望在F#中有”break”,但我从来没有这样想过”return” –我想不起来有任何地方需要它。 )
在C #中,你通过”throw”关键字抛出异常。而在F #中 ,则使用“raise”实现这个功能:
throw new Exception("boom");    // C#

    raise <| new Exception("boom") // F#

在F#中,这是为数不多的我认为应该使用向后管道操作符”<|”的地方. 如果没有它,F#的优先级规则要求我们,必须用小括号括住这个表达式:
    raise( new Exception("boom") ) // F#

我想大多数人都会认为这样看起来很笨拙。
C #中有一个try -catch-finally的异常处理。你可以像这样写:
try
    {
        SomeCode();
    }
catch (NullReferenceException nre)
    {
// swallow it
    }
catch (Exception e)
    {
throw;
    }
finally
    {
        SomeOtherCode();
    }

相对应的,在F #中,代码将是:
try
try
            SomeCode()
with
| :? NullReferenceException as nre -> () // swallow it
| e -> rethrow()
finally
        SomeOtherCode()
在这里,有些东西我必须指出来。首先, F#中,try-with 和 try-finall是两个不同的构造-没有try-with-finally。在实践中,你通常只能做其中一个(一般你使用try-finally的频率几乎是try-catch的十倍,无论是哪一种语言-捕捉异常很少是正确的事情) 。 F # “catch”模块,只需使用模式匹配检测异常的类型,就像我们前面看到的C#中“is”/“as”的翻译一样。在F #中 ,重新抛出的异常,请使用“ rethrow ( ) ”。
C #中有一个“checked”语句检查算术溢出,F#在库中有checked操作符,所以如果你需要溢出检测,只需打开此命名空间,并使用该操作符就行了。
C #中有一个“lock”语句可以锁定对象的一个关键部分:
lock (o)             // C# lock
    {
        SomeCode();
    }
的F # , “lock”是一个函数,可以用来锁定对象和一个兰姆达表达式的关键部分:
lock o (fun () -> // F# lock
        SomeCode()
)
C #中有对于IDisposables的using语句 :
using (var disp = someDisposable)
    {
        SomeCode();
    }
F #的相应的语法:
    use disp = someDisposable 
    SomeCode()
F #的“use”相当于“let” ,它的范围一直到周围块的结尾。

在C#方法中返回IEnumerabl, 可以通过下面的的”yield”语句实现:

yield return 42;  // C# yield a value
yield break;      // C# end the enumeration

在F#中,你不需要声明method-returning-IEnumerable,取而代之的,你可以在任何地方使用”seq{…}”产生序列表达式:
let myIntSeq : IEnumerable<int> = seq { yield 42; }
我加入了类型说明,目的是提醒你”seq”在F#中代表“ IEnumerable ” 。因此,上面那行代码右边的序列表达式,对IEnumerable <int>求值产量一个单一的值( 42 ) 。就像C#代码中的迭代块一样,在F#序列表达式中,你可以通过多种形式使用yield。但是对于C#中的”break”,F#中没有相应的”yield break”。
结尾
我想,到目前为止,这篇博文只是涵盖了C#表达式和语句中所有有趣的内容,阐述了F#中相对应的部分。我希望这对于你学习F#知识,能够起到有益的补充作用.

posted on 2009-10-21 10:29:17 by franceslam  评论(0) 阅读(1637)

Powered by: Joycode.MVC引擎 0.5.2.0