我的博客

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

导航

每月存档

广告

 

原文出处: 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

打印 | 张贴于 2009-10-21 10:34:01 | Tag:暂无标签

留言反馈

暂时没有留言纪录
博客主人设置本博客不允许匿名用户发表言论,请登录后再试

Powered by: Joycode.MVC引擎 0.5.2.0