鼓励使用F#等式与比较约束

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

翻译:Sean Woo

Don刚发了一篇很好的博客记载,来描述F#最新版中等式与比较约束的细节。我将花一点时间来描述从更高层角度激发这个特征。读这盘博客之前,读者应该学习

  • 什么是结构化相等,结构化比较

  • F#的相等、比较约束解决了什么问题

  • 为什么这个问题没有一个特定的语言特征就无法解决

  • 程序员需要在什么时候注意到这个

如果你想知道关于这部分中F#语言特征的细节以及怎样去练习很好的控制和规范你的 用户自定义类型的相等、比较,那么你就需要读一下Don发表的文章。另一方面,如果你想得到的是这方面的总览以及更多的关于“为什么”的例子,希望这篇文章能给你帮助。一些读者可能会发现这篇文章是Don那篇文章的一篇非常有用的前序。

那么,我们开始吧!

什么是结构化的相等和比较?

先来看下面这段代码:

let il1 : int list = [ 1; 2; 3 ]

let il2 : int list = [ 2; 3; 1 ]

printfn "%A" (il1 = il2)

printfn "%A" (il1 < il2)

输出结果是什么?如果你说是“假”或“真”,那么你已经有了很好的对结构化相等和比较的感觉。但是,我们需要更多的关注细节。

结构化相等意味着对F#结构类型(例如,闭包,数组,列表,类型,记录和联合)来说,我们能得到用“相等”来比较每个单独的结构中元素相等的默认动作。那就是,一个“整数列表”等于另外一个“整数列表”意味着,他们具有相同的长度,以及每一个单独的元素都相等。(把这个和一个默认为引用相等类的.Equals()方法的默认行为相比)结构化相等通常是写这个类型的人想要的语意,在F#中,你能在任何结构类型中“自由的”了解语意。以我的经验来说,大部分人都对结构化相等含义和其用法有很好的直觉。(技术层来说:结构化相等实现了一个类型的.Equals().GetHashCode()方法,就如同F#中的“=”运算符和哈希函数。)

结构化比较类似于结构化相等,除了它实现的比较运算符(<, <=, >, >=, 这些调用System.IComparable.CompareTo())。对一些人来说,对这个可能没直觉用小于号比较两个整数列表是什么意思?为了来解释这个,我引申一个更加熟悉的领域:按字符串排序的字典。如果我问你字典里badaceada这些词出现的顺序,我打赌你会告诉我。那么你对下面这段F#代码一定不会觉得惊奇:

 

let
words = [| "bad"; "ace"; "ad"; "a" |]

printfn "%A" (Array.sort words)

// [|"a"; "ace"; "ad"; "bad"|]

字典顺序,有时称为辞典编撰顺序,是一个非常有用的结构化类型语义。(旁白:你可以回想我在之前博客中提到的将结构化比较用于自动排序扑克牌。)脑子里有之前例子中的代码,那么我希望现在这些代码看起来不会那么让人吃惊:

let intLists = [| [2;1;4]; [1;3;5]; [1;4]; [1] |]

printfn "%A" (Array.sort intLists)

// [|[1]; [1; 3; 5]; [1; 4]; [2; 1; 4]|]

我所有做的就是从字符串(结构上来说只是字符的列表)改成了整数列表(在这个例子中,可以看成a=1, b=2等)。

这个例子中,[1;2]<[2;1],因为我们能比较它们第一个元素并且马上就能发现不同之处,而对[1;2]<[1;3]因为我们能发现它们第一个元素是一样的,那么我们就来比较下一个元素,直到发现不同的为止。这个逻辑也同样应用到其他结构化类型,例如闭包:(1,”bbb”)<(2,”aaa”)(1,”aaa”)<(1,”bbb”);记录:{field1=1;field2=”aaa”}<{field1=1;field2=”bbb”};等等

要把他们都相加,结构化相等和比较定义了大多数如等号和小于号这样自然的结构化类型语义。F#中,你能“自由”的得到结构化类型中的这些语意。(你也能不选择这些默认行为,或者自己定义相等、比较行为,更多细节请看Don的文章。)

现在我们知道了结

发表在 未分类 | 一条评论

使用VS2010来编辑F#源代码

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

翻译: Sean Woo

我做了另外一个示范视频。这段视频包含了Visual Studio编辑器的一些特征;背景是一些做logo(乌龟图)的代码。视频在这里(少许点击才能下载;我还在做一些新文件来嵌入到视频中)。这是一个11分钟的视频,包含了以下内容:

  • 标记高亮(颜色,字体,大小)

  • 智能化(自动完成,参数帮助)

  • 下划波浪线(错误,警告,导航)

  • 类型判断(提示)

  • 到定义处,导航,查找

  • 代码模块的注释、取消注释

如果你是F#新手,我希望这能很好的帮助你学到更多关于Visual Studio开发环境的东西。如果你有比较多的经验,你或许也会学到一个新的键盘快捷方式,或者发现一个之前不知道的VS的特色。

在视频中的代码是为画logo写的一小段嵌入式语言。完整的代码如下;你只需要复制到F#应用程序工程里并且运行就可以了。它使用的是WPF图像,所有你需要在标准WPF元件(PresentationCorePresentationFrameworkSystem.Xaml System.Xml UIAutomationTypes WindowsBase)加入到引用中。祝你愉快!

open System

open System.Windows

open System.Windows.Controls

open System.Windows.Shapes

open System.Windows.Media

 

[<Measure>]

type deg

[<Measure>]

type rad = 1

 

type Command =

    | Fwd of int

    | Left of int

    | Right of int

    | PenUp

    | PenDown

    | Repeat of int * Command list

 

let Repeat n cmds = Repeat(n,cmds)  // for syntactic elegance of EDSL

 

let MillisecondsToSleepBetweenCommands = 30

let WIDTH = 400.0

let HEIGHT = 300.0

let PI = System.Math.PI

let deg2rad (d:float<deg>) : float<rad> = d * PI / 180.0<deg>

 

type MyWindow(theDrawing) as this =

    inherit Window(Title="Fun LOGO drawing")

   

    let canvas = new Canvas(Width=WIDTH, Height=HEIGHT)

    let mutable curX = WIDTH / 2.0

    let mutable curY = HEIGHT / 2.0

    let mutable curT = 0.0<deg>

    let mutable isPenDown = true

    let turtle =

        // a right-pointing triangle centered about the origin

        let points = PointCollection [  Point( -5.0, -10.0)

                                        Point(  5.0,   0.0)

   &#16
0;                     
              Point( -5.0,  10.0)

                                        Point( -5.0, -10.0)  ]

        points.Freeze()

        let poly = new Polygon(Points = points, Stroke = Brushes.Green)

        canvas.Children.Add poly |> ignore

        poly

    let update() = async {

        Canvas.SetTop(turtle, curY)

        Canvas.SetLeft(turtle, curX)

        turtle.RenderTransform <- new RotateTransform(Angle=float curT)

        do! Async.Sleep MillisecondsToSleepBetweenCommands

        }

    do

        Canvas.SetTop(turtle, curY)

        Canvas.SetTop(turtle, curX)

        this.Content <- canvas

        this.SizeToContent <- SizeToContent.WidthAndHeight

        this.Loaded.Add (fun _ ->

            async {

                do! Async.Sleep 200

                for cmd in theDrawing do

                    do! this.Execute(cmd)

            } |> Async.StartImmediate

        )

 

    /// <summary>

    /// Execute a single LOGO command and update the screen

    /// </summary>

    /// <param name="command">The Command to be executed</param>

    member this.Execute command = async {

        match command with

        | PenUp ->

            isPenDown <- false

        | PenDown ->

            isPenDown <- true

        | Fwd n ->

            let t = deg2rad curT

            let newX = curX + float n * cos t

            let newY = curY + float n * sin t

            if isPenDown then

                let line = new Line(X1=curX, X2=newX, Y1=curY, Y2=newY, Stroke = Brushes.Black)

                canvas.Children.Add line |> ignore

            curX <- newX

            curY <- newY

            do! update()

        | Left t ->

            curT <- curT - float t * 1.0<deg>

            do! update()

        | Right t ->

            curT <- curT + float t * 1.0<deg>

            do! update()

        | Repeat(n,cmds) ->

            for _ in 1..n do

                for cmd in cmds do

                    do! this.Execute cmd

        }

 

let EYE = Repeat 1 [ PenDown; Repeat 8 [ Fwd 20; Right 45 ]; PenUp ]

 

let DRAWING = [ // go to good start location

                PenUp

                Left 135

                Fwd 80

                Right 135

                // left eye

                EYE

                // move right

                Fwd 80

                // right eye

                EYE

                // go to mouth corner

                Fwd 60

                Right 90

                Fwd 90

                // smile

                PenDown

                Right 45

                Fwd 60

                Right 45

                Fwd 100

                Right 45

                Fwd 60

                // circle around face

                PenUp

                Left 135

                Fwd 80

                Left 90

                Fwd 30

                PenDown

                Repeat 8 [

                    Fwd 100

                    Left 45

                ]

              ]

 

[<STAThread>]

do

    let app = new Application()

    app.Run(new MyWindow(DRAWING)) |> ignore

发表在 未分类 | 评论关闭

九个使用F#的理由

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

翻译:Sean Woo

(开场白:Robin MilnerML之父,最近去世了,Donblog简短的追溯了Milner工作对.NET泛型和F#类型推断的影响。作为一名编程语言爱好者,我喜欢学习语言的历史,以及各种语言特征之间不停的互相影响。)

最近我们更完成了VS2010这个产品,之前我少花了很多时间来写代码,多花了时间来演讲;在之前博客中我提到过,我最近在准备一个TechReady,同时我还要为Alt.NETChris Smith做一个F#研讨会。为演讲做准备的过程,让我有机会来回顾和思考F#如何来演好这场大演出,以及各位观众的感觉是怎么样的。有很多种理由人们可能会发现F#有用,但是大部分演讲、演示仅仅集中于一到两点。我想组织一下我的思路,于是就有了这篇博客。

不用太多思考,这有9个理由来使用F#

1. 测量单元。如果你正在使用数字表示物理数量(如千克,米和秒,或者像素、英尺,美元、欧元,等)的领域工作,你能使用F#类型系统来保证量纲分析很好的工作。几乎每个人使用这个特性都会有一种“太棒了”的体验,因为编译器能找到所有不希望看到的问题(我是在2008 ICFP编程的过程中体会的)。在一些特定领域,这类静态的类型检查真是一个要人命的特性。你能从学到更多。

2. .NET的函数式编程。如果你需要写一个尤其经得起函数式编程技术考验的组件(比如游戏人工智能,静态模型,符号编程),你需要与SQLExcel,或者Xbox交互,或者在Silverlight浏览器中运行,或者在Azure的云中漫步,作WCFWPF,或者WinForm,或者简单的与现有的C#/VB/C++/COM代码进行对话,那么F#就是了。.NET平台能有效的使用各种技术,并与之交互,而F#扩展了这个平台,通过提供一种作第一类函数式编程和裕各种技术做无缝交互的方式。

3. REPL来探索编程。F#交互工具窗口能更简单的运行一小段代码。当你正使用大数据或者数学模型工作的时候,这会使得浏览数据更加方便,玩“什么-如果”的游戏,而且能在与数据/模型交互时用一种很“实时”的方式。当为数据虚拟化与库连接时,这开启了一些真的很酷的东西

4. 异步和并行编程。我最近写了关于F#的唯一的编程模型,用于写异步I/O不会被阻塞的代码。异步编程模型也对并行的CPU加强工作作了扩展,而F#类库也使事件响应,使用轻量级的,或者运行GPU代码等变得更加直观。(实际上网上有很多关于这观点的文章,我就在Donblog里就发现很多。)多核在这,F#语言结构和类库非常适合做并行编程(函数式编程风格一些本身就有的优势就不提了)。

5. 嵌入式领域特定的语言(EDSLs)。F#特征中的一个变量(包括定义新运算符,闭包和函数应用程序标记,注解,类型推断和所有轻量级标记)使得F#是创建EDSL的一门好语言。一些例子,包括FsUnit来做单元测试,FAKE来创建脚本,FParsec来分析,WebSharper来使用Web应用程序。

6. 脚本。经常会发生,我需要写一个脚本,但是我很少使用perl或者批处理文件,因为我一直忘了这些语言的标记。现在我使用F#来写很多脚本任务,都是因为 FSI就是个新的perl”以及这是些很小的任务(例如,如果我不知道字母Aascii值,我就只需要再F#交互窗口中打出这样的就可以了“int ‘A’;;”)。我至少知道一些之前使用批处理文件或者perl脚本的人现在都在使用.fsx文件了。对我来说,我知道F#一开始是一门“软件工程师”语言,同时也是一门很好的“脚本语言”。

7. 学术界的一门实用语言。回到我的大学马上要毕业的时候,我在学校里学到的第一门真正的编程语言是Pascal。(是的,比C#,甚至Java更早,我是个老头子。)在大学里,好像在教更多“纯粹的”与“计算机技术”的语言和教更多的实用主义语言之间有总是有矛盾(比如,你能在离开学校时候轻易的找到工作)。这仍旧是一个争议颇多的话题,但是我认为我们现在的情况好多了,现在用包括JavaC#ScalaPythonF#,而我们那时可是十几二十年前的事情;现在有很
多同时在“教授计算机技术的好语言”和“对现实社会有用”都表现很好的语言。

8. 扩展你的思维和职业生涯。我认为有不少人在他们的日常生活中使用C#或者VB,他们能学更多关于函数式编程,但一深入,发现Haskell太难而且太花时间。F#提供了一条更优雅的通往函数式编程的大道,取决于他现成的.NET Visual Studio的熟悉程度。很多仅把F#作为爱好的人们说是他们学到了一些能用在他们日常工作中的新策略、算法(比如使用C#),太好了!

9. 乐趣!我时常听到人们说他们有多喜欢使用F#。谁不喜欢把工作搞定的同时又充满乐趣呢?

在一篇F#的博客文章,或者视频中,很轻松(而且也确实合适)来“记住”这个语言,集中在一个特定的有用的方面。但是有时回顾和看看人们感觉和使用F#的无数方式是很重要的。所以这是一次尝试,一个分散的列表,用来列出各种人们为什么在基于爱好,学术或者现实中的开发者使用F#的原因。这门语言该上路了,现在就要看看人们怎么去实现所有的方式。

顺便说,有一种来知道微软产品是“真的”的方式就是当他们给你一件背后印有产品标志的T恤。这就是一张我们(VS语言组人们)刚拿到的T恤背面的照片:

clip_image001

发表在 未分类 | 评论关闭

F# 快速指南: 面向对象编程

.gif
发表在 未分类 | 评论关闭

一个关于语言特征和类库设计之间相互影响的例子(二)

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

翻译: Sean Woo

上次我们重申了一个字典查询API的设计,讨论了不同的语言特征,如”泛型”,”异常”和”输出函数”,对API设计的影响.在博客的最后,我们已经将API变成了如下:

// if key not present, returns false
// else returns true and sets value
bool TryGetValue(string key, out int value);

同时客户端代码:

int r; 
if (dict.TryGetValue("Brian", out r)) 
    { 
        Console.WriteLine("Brian’s favorite 32-bit integer is {0}", r); 
    }

这种设计的选择解决了一大堆在推敲这个简单API设计可能的过程中碰到的问题.

此外,我经常找到一些TryGetValue API的稍微令人讨厌的东西.它重新介绍了一个在之前’异常版本’的API中已经解决了的次要缺陷.在异常版本中重新调用客户端代码如下:

try
    { 
int r = dict.GetValue("Brian"); 
        Console.WriteLine("Brian’s favorite 32-bit integer is {0}", r); 
    } 
catch (KeyNotFoundException) 
    { 
    }

对所有的问题,异常版本的API比”TryGetValue”有一个优点,就是,你能只引用在”r”有个有用的值时的它的结果.如果字典中不含有这个键,那么将抛出一个异常,”r”的值将在范围之外被丢弃,一次你将不会有不慎使用它的情况发生.尽管使用TryGetValue方法,我有点粗心而且这样写了代码:

int r;   
if (dict.TryGetValue("Brian", out r))   
    {   
        Console.WriteLine("Brian’s favorite 32-bit integer is {0}", r); 
    } 
    DoSomethingWith(r);  // oops

或者

int r;   
    dict.TryGetValue("Brian", out r);  // oops
    Console.WriteLine("Brian’s favorite 32-bit integer is {0}", r);

TryGetValue API是经过充分设计的,它的签名能引导你如何正确的实用它,但是依然可能意外的使用一个没有意义的”结果”.

那么,让我们再做一个这个API的设计…

模式匹配来拯救!

我们能介绍另外一个语言特征来解决这个问题.模式匹配!我简单地写了笼统介绍模式匹配的,并且特别介绍了F#的”option”类型,在之前的博客记载中.让我们把它投入使用吧.我们的一个新的API方法,现在使用F#的标记法,用它的签名:

// TryGetValue : string -> option<int>

这里的返回类型是一个option<int>.因此客户端代码如下:

match dict.TryGetValue "Brian" with
| Some(r) -> Console.WriteLine("Brian’s favorite 32-bit integer is {0}", r)
| None    -> ()

太完美了!模式匹配是一个控制结构(像”if”—我们要么将代码运行到一些分支中,要么不进入任何分支),同时又是一个绑定机制(我们把一个新名字”r”和一个值关联在一起).作为结果,这种语言机制直接解决了最后的缺陷;现在”r”局限在只有在它有意义的分支上.

尽管”TryParse”模式(用一个布尔返回值和一个输出参数)在C#这样的语言中,这样的设计模式会更好,在有模式匹配的语言中,比如说F#,直接返回一个option<’a>将会是更好的设计模式.像这样的一个API将会有所有之前博客记载中提到过的好处,再加上一条:你不可能意外的误用这个API来得到一个没有意义的结果.

故事的结尾

这次,真的是故事的结尾了.虽然,刚刚演示过模式匹配的这个讨人喜欢的好处,我想喊出它怎么关系到写地道的F#代码.

假设我们正在写一个非常简单的函数来指出一个列表中的所有数字.一个非常不地道的F#代码写法是:

// PrintNums : list<int> -> unit
let rec PrintNums (l : list<int>) =
if not l.IsNil then
        printfn "%d" l.Head
        PrintNums l.Tail

这部分最明显不地道的地方时使用了if,以及调用.IsNil,.Head和.Tail时用到的if-then结构.你基本上不会这样写F#代码,因为这样容易造成错误…实际上,当写像上面这个例子,我实际上也同样犯了一个错误,原来是忘了if条件下的”非”的情况.显然,if条件弄错将会导致随后的代码失败.作为结果,使用模式匹配就显得地道多了:

// PrintNums : list<int> -> unit
let rec PrintNums l =
match l with
| h :: t -> printfn "%d" h
                PrintNums t
| [] -> ()

现在,如果我们依照被模式匹配绑定的”h”和”t”来写代码,那么我们就不会无意间犯我之前犯得错误(反问” 如果为空”分支中列表的头部).模式匹配帮助我们避免写保函一些特定逻辑错误类的代码.(一个副效益,如果代码已经这样写了,我们仍旧得到参数类型”l”的类型推断.)

我的观点是,当模式匹配可用的时候,你应该优先使用它来写你的控制结构(例:if-then-else)或者数据的选择部分(例:accessing .Head),因为模式匹配语言标记和类型系统是设计成用来限制你写代码的方法,以至于你想犯错误都难.

(我最后写了一个更加优化,但是也完全地道的方式的PrintNums

// PrintNums : list<int> -> unit
let PrintNums = List.iter (fun x -> printfn "%d" x)

但是这样写不会让我讨论更多模式匹配的东西)

发表在 未分类 | 评论关闭

一个关于语言特征和类库设计之间相互影响的例子(一)

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

翻译: Sean Woo

在今天的博客记载中,我想追述一个想象的世系— 一个简单的API方法的集合,来演示语言设计,类库设计和编程习惯用语随时间的变化过程.问题中说的API是一个字典的查找功能,而且我认为仔细学习这个相关的平凡方法能提供一些有关编程语言结构,类库和习惯用语间相互影响的吸引人的见识.

今天的博客,我将会使用C#标记,但是仅仅为了标记方便,因为我的意图是用完全语言中性化的方式来讨论这些.

一个非常简单的字典查询

我们需要一个把字符串值映射到整数值的表格.(也许,字符串表示为一个名字,像”Brian”,而整数表示这个月发表的博客记载的数量.)假设,我们在一个介绍性的编程类中(或者也许在使用一种非常古老的语言)我们还不知道它的泛型或者异常.我们可能在这个”字典”类中用像这样的开一个实例方法来设计一个”查询”API:

// returns -1 if key not in dictionary
int GetValue(string key);

然后,调用它的客户端代码如下:

int r = dict.GetValue("Brian");
if (r != -1)
    {
        Console.WriteLine("Brian posted {0} blogs", r);
    }

这个API很简单,而且对这个例子来说是有效的.

值间的问题

之前的GetValue()函数在查询结果中使用了一个特殊的值(就是”-1”) ,用来提供一种方法来报告是否字典里实际上包含了一个”Brian”的记录.当结果中存储的是一个非负的整数(例如,我发表的博客记载数量),当然是好的,但是这个方案在我们需要存储该数据类型的整个值空间的时候将会失败.所以.例如,再一次假设我想要一个字符串映射到整数的结构,字符串的键值依然是一个人的名字,但是这次整数值代表的是这个人最喜欢的一个32位整数.我们就不能再用”-1”来作为意味着”不在字典里”的代表值,因为你不能够把它从一个真正喜欢-1这个数的人的记录中区别开来.(注意:如果我们要把这个字典在值类型上更泛型化,代表值也将会是不予考虑的.) 所以我们可能把这个API改变如下:

// returns true if the dictionary contains the key
bool ContainsKey(string key);
// (undefined behavior if key not in dictionary)
int GetValue(string key);

同样,客户端代码如下:

if (dict.ContainsKey("Brian"))
    {
int r = dict.GetValue("Brian");
        Console.WriteLine("Brian’s favorite 32-bit integer is {0}", r);
    }

我们已经解决了不允许使用一个特定的值代表”不包含”的问题(并因此,这个API使得写一个”泛型”的字典类成为了可能).尽管还在写,我们提出了一些新问题.首先,这个API变得更加复杂了—用户需要知道两个方法而不是之前的只有一个.此外,API的行为需要客户端使用一个特定的习语—客户端代码必须在调用GetValue()之前调用ContainsKey()来保证这个行为是定义好的.这样可能会变得没什么效率了,因为我们现在要在数据结构中查找这个值两次(一次在ContainsKey()中调用,在GetValue()调用中又一次).最后,如果这个数据结构能被多个执行线程同时调用到,我们需要客户端把两个调用到的数据结构锁定,或者也可能ContainsKey()会成功,而一些其他的线程可能在GetValue()运行之前就将这个键值对删除了.真是太糟糕了!

用异常来拯救?

一个可行的方法来避免这种混乱是采用一种新的语言特征—异常.如果我们的编程语言支持异常,我们能像这样重定义这个API:

// throws KeyNotFoundException if key is not in dictionary
int GetValue(string key);

客户端代码就变成:

try
    {
int r = dict.GetValue("Brian");
        Console.WriteLine("Brian’s favorite 32-bit integer is {0}", r);
    }
catch (KeyNotFoundException)
    {
    }

“异常”这个语言特性仅仅是帮助解决所有之前我们在API中提到的四个问题.我们又回到只有一个方法了,它不需要特定的语言调用顺序,它只需要查找一次,而且能在方法内部做线程同步.真是个大改进!

然而,这个API的设计依然是不好的,因为异常是一个非常重量级的机制.在很多语言中,抛出一个异常意味着你的代码表现将是不好的(无论如何,比一个if-then-else结构要更糟).抛出一个异常意味着你将扰乱调试的体验(例如,那些使用’抓住所有第一次机会异常’来找到真正代码问题的).并且异常在语法上给调用制造额外的负担(通过一个try-catch结构).所以我们的API仍然还不好.

输出参数来拯救…

我们能介绍另外一种语言特征来处理这些问题.如果我们的编程语言支持”输出”参数,我们可以像下面这样重写API:

// if key not present, returns false
// else returns true and sets value
bool TryGetValue(string key, out int value);

那么客户端代码就变成:

int r;
if (dict.TryGetValue("Brian", out r))
    {
        Console.WriteLine("Brian’s favorite 32-bit integer is {0}", r);
    }

再一次,我们解决了之前尝试中提到的问题.这个API简单而且表现良好.的确,这个API是被System.Collections.Generic.Dictionary使用的,而且是一个更加常规的设计语法(叫做TrayParse式样,具体的描述点这里这里)的一个实例.(如果你之前还没有读过”TryParse”,点击那些链接先学习一下)

故事的结束?

我将先讲到这里.我们刚反复讲了API的四种设计,通过修复之前设计中的缺陷,不断的改进方案.通过这个方法我们看到了一些语言特征是怎样与类库的API设计互相影响,而且也影响了调用代码的语法.

但是,还有很多要说的.尽管,我将留到下次博客中再谈.(开个玩笑~)

发表在 未分类 | 评论关闭

F#中的可识别联合

(Discriminated unions in F#)

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

翻译: Sean Woo

在F#的代码中,经常会用到可识别联合. 一个可识别联合是一种数据类型,它具有一定数量独特且可供选择的表现. 如果你在C/C++中使用过”union”, 你能把可识别联合作为类似的一种结构类型; 主要的不同点在于, F#中的可识别联合是类型安全的(每个实例都知道, 哪个特征是’激活’的), 同时F# 的可识别联合可以与模式匹配一起使用. 这是一个简短的例子, 在如何定义和使用可识别联合类型时可供参考:

type ADiscriminatedUnionType =
| Alternative1 of int * string
| Alternative2 of bool
| Alternative3
// constructing instances
let du1 = Alternative1(42,"life")
let du3 = Alternative3
let f du = 
// pattern matching
match du with
| Alternative1(x,s) -> printfn "x is %d, s is %s" x s
| Alternative2(b) -> printfn "b is %A" b
| Alternative3 -> printfn "three"

f du1  // x is 42, s is life
f du3  // three

The list<’a> type

一个F#中最常用的类型, list<’a>, 是一个可识别联合. 它具有两种可能性: 空(用”[]”表示), 和包含元素(用”::”表示). 前者不包含数据,后者是 ‘a * list<’a>类型. 因此,你很可能写下如下的代码:

match l with
| [] -> printfn "do something in nil case"
| h :: t -> printfn "do something else in cons case"

在F#中,如果”l”是list<int>类型,那么”h”将是一个int(一个列表的头部—就是说,第一个元素),而”t”将是list<int>(列表的尾巴—就是指,除了第一个元素的其他部分). 列表模式匹配的存在意味着你很少调用List.hd, List.tl和List.noneempty(可查看List module)这样的函数. 你可以发现上面的代码大致和下面的C#代码有相同的意思:

if (l.IsEmpty)
{
    Console.WriteLine("do something in nil case");
}
else
{
var h = l.Head;
var t = l.Tail;
    Console.WriteLine("do something else in cons case");
}

实际上,模式匹配在一个控制结构(比如if-then-else或者switch表达式)和一个赋值结构(使用”let”将一个值绑定到一个名字)的时候是同步的.在你大量练习使用模式匹配之前,你很难了解模式匹配是多么的实用和优雅.(我之前在ML 读过有关模式匹配,几年之前,但是我仅仅将它作为一种讨人喜欢的标记而忽略了. 但是现在我在F#中使用它, 它真是非常方便,而且我也最终了解了为什么 ML的人经常吹捧这个语言功能.)

The option<’a> type

它是list<’a>的一部分, 在F#中另一个最常用的可识别联合类型就是option<’a>. 显然,它很简单:

type option<’a> =
| Some of ‘a
| None

这个类型基本上会经常用在需要或不需要返回一个结果的函数上.例如,如果我们想找到列表中某个和一些判定匹配的元素,这个列表有可能会包含这个元素.那么,List.tryfind返回一个option.这是一个实现的例子:

let PrintFirstStartsWithM l =
let answer = List.tryfind (fun (s : String) -> s.StartsWith("M")) l
match answer with
| Some(s) -> printfn "%s" s
| None -> printfn "list had no strings starting with M"

PrintFirstStartsWithM ["Sunday"; "Monday"; "Tuesday"] // Monday
PrintFirstStartsWithM ["one"; "two"; "three"] // list had no strings starting with M

如果你对TryGetValue in System.Collections.Generic.Dictionary这个方法熟悉,那么你对option<’a>怎样处理类似TryGetValue(或者更普遍的,”TryParse”的设计模式,简短的介绍点这里)签名问题将会非常清楚.

何时使用可识别联合

在一种类型,它具有一定数量独特的表现,而你想变成模式匹配的时候,可以用可识别联合.这意味着,你能经常将一个小型类层次结构表示为一个可识别联合.例如,之前博客(入口点这里)中提到的”PrettyString”代码.之前我写到那段代码的时候是用一个小型类层次结构;这里我将用一个可识别联合代替.(我在”#if CLASSES”部分保留了类代码.)

#light
open System 
open System.Text 
open Microsoft.FSharp.Collections 
#if CLASSES
/// PrettyStrings are strings that support vertical and horizontal concatenation
/// for creating grids of text.
[<AbstractClass>]
type PrettyString() = 
/// The number of lines in this PrettyString
abstract Height : int
/// The width of this PrettyString (width of the longest line)
abstract Width : int
/// Get the nth line.  If n is not in the range [0..Height), then return the empty string.
abstract Line : int -> string
override this.ToString() =  
let sb = new StringBuilder()
for i in 0 .. this.Height do
            sb.Append(this.Line i) |> ignore
            sb.Append("\n") |> ignore
        sb.ToString()
type StringLiteral(s : String) = 
inherit PrettyString()
// TODO: if the input string contains newline characters,
// things will be ugly.  Ignoring that issue for now.
override this.Height = 1
override this.Width = s.Length 
override this.Line n = if n <> 0 then "" else s 
type Vertical(top : PrettyString, bottom : PrettyString) = 
inherit PrettyString ()
override this.Height = top.Height + bottom.Height 
override this.Width = Math.Max(top.Width, bottom.Width)
override this.Line n = 
if n < 0 || n >= this.Height  
then ""
else if n < top.Height 
then top.Line n 
else bottom.Line (n - top.Height)
type Horizontal(left : PrettyString, right : PrettyString) = 
inherit PrettyString()
override this.Height = Math.Max(left.Height, right.Height)
override this.Width = left.Width + right.Width 
override this.Line n = 
        String.Concat(left.Line(n).PadRight(left.Width), 
                      right.Line(n))
let pretty s = new StringLiteral(s) :> PrettyString  
let (%%) x y = new Vertical(x,y) :> PrettyString 
let (++) x y = new Horizontal(x,y) :> PrettyString

#else

type PrettyString =
| StringLiteral of String
| Vertical of PrettyString * PrettyString
| Horizontal of PrettyString * PrettyString
let rec Height ps =
match ps with

| StringLiteral(_) -> 1
| Vertical(top, bottom) -> (Height top) + (Height bottom)
| Horizontal(left, right) -> max (Height left) (Height right)
let rec Width ps =
match ps with
| StringLiteral(s) -> s.Length
| Vertical(top, bottom) -> max (Width top) (Width bottom)
| Horizontal(left, right) -> (Width left) + (Width right)
let rec Line ps n =
match ps with
| StringLiteral(s) -> if n <> 0 then "" else s
| Vertical(top, bottom) ->
if n < 0 || n >= Height ps
then ""
else if n < Height top
then Line top n 
else Line bottom (n - Height top)
| Horizontal(left, right) ->
        String.Concat((Line left n).PadRight(Width left), 
                      Line right n)
let ToString ps =
let sb = new StringBuilder()
for i in 0 .. Height ps do
        sb.Append(Line ps i) |> ignore
        sb.Append("\n") |> ignore
    sb.ToString()
let pretty s = StringLiteral(s)
let (%%) x y = Vertical(x,y)
let (++) x y = Horizontal(x,y)
#endif
let blank = pretty " "
let calendar year month = 
let maxDays = DateTime.DaysInMonth(year, month)
// make the column that starts with day i (e.g. 1, 8, 15, 22, 29)
let makeColumn i = 
let prettyNum (i:int) = pretty(i.ToString().PadLeft(2))
let mutable r = prettyNum i 
let mutable j = i + 7
while j <= maxDays do
            r <- r %% prettyNum j 
            j <- j + 7
        r 
let firstOfMonth = new DateTime(year, month, 1)
let firstDayOfWeek = Enum.to_int firstOfMonth.DayOfWeek
// create all the columns for this month's calendar, with Sundays in columns[0]
let columns = Array.create 7 blank 
for i in 0 .. 6 do
        columns.[(i + firstDayOfWeek) % 7] <- makeColumn (i+1)
// if, for example, the first of the month is a Tuesday, then we need Sunday and Monday
// to have a blank in the first row of the calendar   
if firstDayOfWeek <> 0 then
for i in 0 .. firstDayOfWeek-1 do
            columns.[i] <- blank %% columns.[i]
// put the weekday headers at the top of each column
let dayAbbrs = [| "Su"; "Mo"; "Tu"; "We"; "Th"; "Fr"; "Sa" |]
for i in 0 .. 6 do
        columns.[i] <- pretty(dayAbbrs.[i]) %% columns.[i]
// Horizontally concatenate all of the columns together, with blank space between columns
let calendarBody = Array.fold1_right (fun x y -> x ++ blank ++ y) columns 
// Surely there must be a .NET call that turns a month number into its name,
// but I couldn’t find it when I was typing this up. 
let monthNames = [| "January" ; "February"; "March"; "April"; "May"; "June";  
"July"; "August"; "September"; "October"; "November"; "December" |]
let titleBar = pretty(sprintf "%s %d" monthNames.[month-1] year)
    titleBar %% calendarBody
let c = calendar 2008 4
#if CLASSES
printfn "%s" (c.ToString())
#else
printfn "%s" (ToString c)
#endif

“PrettyString”虚类变成了一个可识别联合类型,而它的每个子类变成了一个可选项.虚类的方法变成了最高级的将PrettyString作为一个参数的功能.此外,几乎没有改变.希望这个例子能够帮助你区分出可识别联合和类层次结构.

一个类层次结构是”开放的”,那么之后其他人能增加一个新的子类,而可识别联合是”封闭的”,在类型式样的作者眼中,所有的可选项就 永远是那样子了(比如说:想象一下一个”密封的”(sealed)类结构).这经常会是如何决定何时使用一个类层次结构和一个可识别联合的主要原因.

今天就到此为止.我已经写了不少博客记载来让可识别联合和模式匹配得到承认,是时候我该停下来,少说明它们一些啦,嗯?

发表在 未分类 | 评论关闭

F#初学者:一些小技巧让你变得更有效率

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

翻译: Sean Woo

我已经写过不少博客记载关于一些F#最酷的功能和最好的编程方面.F#对大部分人来说是崭新的,虽然,在学习一种新的编程语言的时候,一定会有一段令人沮丧的时期,这期间,你在学习新的标记,重新组织一些结构化代码的思维模型.F#小组一直在寻找减少开始学习少走弯路的方法,那么我今天就讲一些新手经常会碰到的话题,我会描述一些有用东西从而让你们知道一些”预备”知识,以便让你在F#上提升更容易.

那么,为了避免更多的麻烦,这有一个非常有用的小技巧,让你能更容易的运行F#成功.

“Tab”字符在#light代码中是非法的

你能发现几乎所有的F#代码中都会使用一个轻量级的标记选项—直接在文件的顶部使用”#light”(将来很大程度将会变成默认的.译者:VS2010中已经是默认的了).当使用#light标记的时候,版面的事情(空格是重要的),”tab”字符是非法的.因此非常多的用户在Visual Studio中浏览F#时,他们会在第一时间开始写他们的第一个函数,按”tab”去调整函数主体的缩进,而突然,他们发现刚刚输入的空格被波形曲线高亮了,显示出是代码错误状态.怎么回事?

幸运的是,解决这个很简单:在Visual Studio中,到” Tools\Options\Text Editor\F#\Tabs”中,选择”Insert spaces”.这么操作一次, 问题会永远解决. 其他关于这个的信息在之前的博客中有说,点击这个链接能看到一张截图.(如果你使用Visual Studio之外的一些编辑器,务必在使用这个编辑器编辑F#源文件时候将tabs都正确转化为空格.)

元组函数和局部套用函数

新接触F#的人们需要学习两种不同的函数调用方法.我之前讨论过使用元组和使用局部套用函数的区别.思考:

// fc : int -> int -> int
let fc x y   = x + y    // 局部套用
// ft : int * int -> int
let ft(x, y) = x + y    // 元组

由于函数能被写成同样的形式,用正确的符号去调用函数就显得尤为重要,例如:

fc 3 4
    ft(3,4)

如果你将一个局部套用函数混淆,用元组的调用方法,或者反过来,你最终会找到类似以下的错误信息:

fc(3,4) // 这个表达式具有 ‘a * ‘b 的类型但是这里使用的是一个int型
    ft 3 4 // 这个值不是一个函数,并且不能被应用

虽然你不需要理解像”currying”(局部套用函数), ”partial application”(部分应用程序)和”higher-order functions”(高阶函数)这样的术语,因为作者的程序将会做类型检查,你得至少知道局部套用和元组的区别.元组函数具有参数被”*”隔开这样的类型,它们被元组的标记(圆括号和逗号)所定义和调用.局部套用函数具有参数被”->”隔开的类型,同样,它们仅仅用空格隔开参数的形式来定义和调用函数.你必须学会这种区别以便于在你第一周的F#编程中存活下来.好消息是,在今天的博客里说的你早就听说过,你已经足够了解,而且你再也不会被”局部调用/元组”的区别这样的问题所挡住.

别怕简单代码中的标记错误

另外一个对第一次写F#的人们有用且普遍的经验是在打字输入的时候使用Visual Studio交互窗口的回馈.假设你要写这样的一个函数:

let circumference r =
let pi = 3.14
let d = 2.0 * r
        pi * d

那是完全合法的F#代码.尽管,当你打字输入的过程中,你会得到这样的提示:

let circumference r = 
let pi = 3.14
let d = 2.0 * r
// 没有完成输入的时候光标在这里

当你还没有完成输入的时候,你会惊奇的发现在最后一个”let”下面有意味着错误的波浪线,像”标记错误”或者”这个let的返回表达式有误”这样的错误信息.这是怎么回事呢?

轻量级标记选项很容易使我们忘记这些函数体中的”let”不是语句,而是一个表达式的开始.(F#和大部分函数型语言一样,倾向于说表达式而不是语句.)F#的语法规则中一个let表达式看起来像” let ident = expr in body-expr”(让 标示 = 表达式 在 表达式主体中).主要的部分是结尾的” body-expr”.一个let表达式必须包含一个主体!由于轻量级标记允许我们省略”in”,主体的必要性尤其容易遗忘.如果我使原来的代码更,更明显:

let circumference r =
begin // 千万别这样写代码;这里仅仅是强调分析过程
let pi = 3.14 in
begin
let d = 2.0 * r in
begin
                            pi * d
end
end
end

那么当你意识到的时候,在原来的函数中,如果你还没打出最后一行代码,对这个错误会更有感触,就比如像下面这样

let circumference r =
begin // don’t write code like this; just emphasizing how it parses
let pi = 3.14 in
begin
let d = 2.0 * r in
// cursor here, haven’t finished typing yet
end
end

现在, ”这个let的返回表达式有误”(指在”let d = …”的let下面)这个错误信息会让你更加明白—在光标处的这个let表达式没有主体!

这儿提到的这解决方法只是让你意识到函数的这个方面,以及怎样与工具结合.Visual Studio中提供的F#语言服务做比C#更多的”在线”语法解析,类型检查和语义分析.在C#中你通常需要明白”编译”是用于得到类型检查的错误,在F#中,语言服务在你打字输入的时候,在后台不停的运行语法重解析和类型检查,从而提供互动信息回馈(对类型推断会很有用).尽管这样的结果是,一下错误报告和波浪线在你依然还在编辑当中有点”太着急”了.尽管我们一直在不停的改进Visual Studio中与F#在编辑时的互动体验,理解这个方面是重要的,而且别让你进入这个怪圈.(一个相关的注意点,我们还有不少改进编译器诊断错误质量的工作需要去做.一个”标记错误”能告诉你问题出在哪儿,但是如果你连正确的标记都不知道,那它不能帮助你摆脱被卡住的情况.)

概要

我们已经讨论了一些简单问题,这些问题几乎每个F#编程新手在他们学习使用这门语言的第一周的都会面对.每个问题都很容易解决,解决一次,其他都会迎刃而解—只要你真正了解,或者使用正确的工具!不要让这些小问题阻止你学习,使用,享受这门语言.如果你发现你卡在某个地方,让我们知道—F#组乐意听到你的回馈—积极地和消极的都行.你最喜欢F#的哪些方面?你觉得这些部分是令人混淆的呢还是令人沮丧的?你有关于F#语言,类库,工具或者安装方面的回馈吗?赶快发送邮件

现在是发送信息回馈最好的时间!你现在能真正用上F#啦,你只要在这里下载最新发布的版本,试一下吧!因为我们从Microsoft Research发布F#到Microsoft Developer Division(将F#发展成为.NET平台

发表在 未分类 | 评论关闭

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”), 却又引起了多少的争议.

发表在 未分类 | 评论关闭

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<_&
gt;”将会把myList向上转换成一个“seq<int>”类型,因为‘_’被推断为‘int’。

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

发表在 未分类 | 评论关闭