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

开发 后端
F#是随着VS2010 Beta版一起正式推出的另一个基于.NET平台的语言,以函数式语言著称。这个F#入门文章介绍了F#的基本语法,模式匹配及List基本类型。

F#随着VSTS 2010 Beta1 发布也有一段时间了,园子里应该也有不少人对它感兴趣吧。下面的例子是我在学F# 基本语法时写的一个简单Sieve of Eratosthenes 实现,通过剖析这一小段代码,我希望大家能对F#有个简单认识,并能自己写一些简单的小程序。

F#入门代码

  1. let GetAllPrimesBefore n =   
  2.     let container = Array.create (n+1) 0  
  3.     let rec loop acc = function  
  4.         |[] -> List.rev acc  
  5.         |hd::tl ->   
  6.             if container.[hd] =1 then   
  7.                 loop acc tl  
  8.             else 
  9.                 for j in [hd .. hd .. n] do 
  10.                     container.[j] <- 1  
  11.                 loop (hd::acc) tl      
  12.     loop [] [2 .. n]  
  13.       
  14. let primesBefore120 = GetAllPrimesBefore 120 

废话少说,直接进入正题吧

  1. let GetAllPrimesBefore n = 

第一行,申明函数GetAllPrimesBefore, 并且该函数有一个参数n, 在这里我没有指定n的类型,因为编绎器可以通过函数体对n的类型进去推断,比如在本例中,n就是int类型,当然我们也可以显示的指定n的类型,比如 let GetAllPrimesBefore (n:int),这样我们就指定了n为int型 (注意:(n:int)中的括号不能省略,let GetAllPrimesBefore n : int 的意思是该函数返回的值的int型)。说完了参数,再说下返回值,同样,编绎器会根据函数体上下文对返回值类型进去推断,所以我们不需要申明返回类型。

  1. let container = Array.create (n+1) 0 

第二行,首先请注意该行与第一行相对有一个缩进({TAB}),F#和Python一样,也是通过{TAB}缩进来组织代码结构的。这一行我们定义了一个变量container,它的类型是Array,大小为 n+1, 并且值全部初使化为0

  1. let rec loop acc = function  
  2.         |[] -> List.rev acc  
  3.         |hd::tl ->   
  4.             if container.[hd] =1 then   
  5.                 loop acc tl  
  6.             else 
  7.                 for j in [hd .. hd .. n] do 
  8.                     container.[j] <- 1  
  9.                 loop (hd::acc) tl  

接下来就是这个函数的主要部分了(原程序中的3-11行),首先我们定义了一个递归函数(我们发现定义递归函数需要加rec关键字)。它接受两个参数,acc和一个List,有朋友可能要问了,这里明明我只看到一个参数acc,你说的那个List在哪呢?可能有细心的朋友也发现了这里的函数定义不光前面有rec,在等号后面还加了个function,那么function是做什么用的呢?

  1. let rec loop acc = function 

F#入门:模式匹配

这里我需要首先讲一下Pattern Matching(模式匹配), Pattern Matching有些类似于C#中的switch语句(当然它要比C#中的switch强大许多,但这不是本文的目地,所以略去不表),可以根据expr的值去执行某一具体分支,它的基本语法也很简单,我们还是结合一个具体实例来看一下(例子比较简单,只是为了说明问题)。 这个例子大家很容易看懂吧,我就不详细解释了,只是说明一点,'_'用来匹配所有别的情况。

  1. let ShowGreeting laguageInUse =   
  2.     match laguageInUse with  
  3.     | "C#" -> printfn "Hello, C# developer!" 
  4.     | "F#" -> printfn "Hello, F# developer!" 
  5.     |_ -> printfn "Hello, other developers!" 

因为Pattern Matching在F#中的使用范围实在太广了,所以就引入了一种简化版,这就是上面大家看到的等号后面的function的作用,我们可以把上面的例子简化成

  1. let ShowGreeting  = function      
  2.     | "C#" -> printfn "Hello, C# developer!" 
  3.     | "F#" -> printfn "Hello, F# developer!" 
  4.     |_ -> printfn "Hello, other developers!" 

怎么样?既少了给参数起名的烦恼,也少敲不少字吧,嘿嘿。

F#入门:List基本类型

接下来我再简单介绍下F#中非常重要的一个基本类型List, 其基本表示形式为 [ item1;item2; .. ;itemn]

F#中List是immutable类型,我们只能访问里面的值,不能改动里面的值,任何改动List的需求只能通过构建新的List来实现。稍一思考,大家就会很快发现要实现一个高效的immutable list, 那最简单的就是对其头结点进去操作了(插入和删除都可以达到O(1),当然插入和删除会构建一个新的List,原List不会改变),F#中的List也是基于这种形式,所有的List都可以看成是Head+Tail(除了Head外的所有结点),F#提供了相应的库函数List.hd, List.tl,并且提供了:: (cons operator)来帮助我们方便的构建一个List,比如1::2::[]就表示List [1;2] (注意1和2之间我用的是;不是, 如果写成[1,2],那个表示该List只有一个元素 (1,2),至于(1,2)是什么类型,为了使文章尽量紧凑,我们今天就不讲了)

有了上面这些知识,再看本文一开始的函数就简单多了

  1. let rec loop acc = function  
  2.        |[] -> List.rev acc  
  3.        |hd::tl ->   
  4.            if container.[hd] =1 then   
  5.                loop acc tl  
  6.            else 
  7.                for j in [hd .. hd .. n] do 
  8.                    container.[j] <- 1  
  9.                loop (hd::acc) tl   

首先,该函数的第二个参数是List,

      当List为空时,就把acc反序返回,

      当List不为空时,把List分成两部分(hd::tl),检查当当前值n (n的值等于td) 是否己被标记

            如果己经被标记(container.[hd] =1),略过当前值,检查接下来的值 loop acc tl

            如果没有被标记(当前值是素数),用当前值和acc构建一个新List (hd::acc),并对当前值的所有倍数进去标记(for loop),然后检查下一个值  loop (hd::acc) tl

这里有两点需要特别说明一下:

      1. container是一个Array类型的参数,Array在F#中是mutable类型的容器,我们可以修改里面的元素,访问元素用Array.[i], 修改元素用Array.<-[i] = newValue(不要忘记中间的.)

      2.  for loop的基本形式为 for <index> in <range> do, 我们可以使用[start .. end]或[start .. step .. end]来构建一个range,当然,这里的range其实也是一个List

看完了内部函数,我们再接着往下看(原程序第12行)

  1. loop [] [2 .. n] 

这里就很简单了,调用我们刚刚定义的内部函数,(acc为空List [], 第二个参数为List [2 .. n]),其返回值(List acc)就是函数GetAllPrimesBefore的返回值,F#中函数有返回值时不需要敲return.

函数调用也很简单,(不需要在参数与函数名之间加括号)

  1. let primesBefore100 = GetAllPrimesBefore 100 

后记

1. F#中函数体内可以定义新的值,变量和函数。(只在当前函数体内可见)。当然,这样做的好处显而易见,我就不啰嗦了。

2. Recursive function是functional programming中很常用的一种算法实现方式。functional programming language往往会针对尾递归进行特别的优化,F#也不例外,所以我们需要尽可能的把递归写成尾递归的形式,这个有时就需要像本文一样借助accumulator来实现。

本文来自hiber的博客:《结合实例学习F#(一) --快速入门》。

【编辑推荐】

  1. C# Actor的尴尬与F#美丽外表下的遗憾
  2. 函数式编程语言F#:基于CLR的另一个头等编程语言
  3. Visual Studio 2010爆F#二进制兼容性问题
  4. 推荐Visual Studio 2010中F#的一些资源
  5. Visual Studio 2010将正式包含F#
责任编辑:yangsai 来源: hiber的博客
相关推荐

2010-01-26 08:25:06

F#语法F#教程

2010-04-07 16:51:59

F#

2010-03-26 19:22:08

F#代理

2010-03-26 19:03:19

F#异步并行模式

2010-03-26 18:31:59

F#异步并行模式

2010-03-16 09:09:04

F#

2010-03-08 09:17:13

F#异步

2009-08-27 09:16:48

F#中DSL原型设计

2010-01-07 10:04:18

F#函数式编程

2010-01-15 08:33:13

F#F#类型推断F#教程

2011-06-09 09:52:41

F#

2009-08-13 17:39:48

F#数据类型Discriminat

2009-08-19 09:42:34

F#并行排序算法

2010-04-06 15:20:56

ASP.NET MVC

2009-09-10 14:18:59

Functional F#

2009-11-16 09:05:46

CodeTimer

2012-11-06 10:01:35

ContinuatioF#

2009-12-04 09:16:44

Visual Stud

2009-12-14 09:04:10

F#运算符

2009-12-11 13:59:35

F#
点赞
收藏

51CTO技术栈公众号