F# 学习之路(3) 如何组织程序(上)

     一、模块(module)

     在F#中模块通常用来封装一组变量和对某种数据结构的操作,例如F#中的List,Map,Seq等模块,这些模块用来针对List列表、Map键值对、Seq序列等数据结构进行操作。

     1、定义一个模块

     在F#中定义一个模块,使用关键字module。如果你在一个源代码文件中(.fs文件)没有显式定义一个模块,F#默认你定义了一个以源文件名大写的模块,其作用域为从文件开头至文件结束,上一篇已经有介绍。  

 

Code

 

 

     文件1定义了一个模块A,定义了一个列表l,文件2 定义了一个模块B,使用print_any函数打印模块B的l列表。

     F#中列表使用方括号,元素之间使用分号分隔,并且一旦定义就不可改变 

     F#中模块有两种,一种是顶级模块,另一种是子模块。所谓顶级模块指的是自身没有嵌套在别的模块中,而一个嵌套在别的模块中的则称为子模块。这两种模块定义的方式也不一样,子模块将在后面的命名空间中介绍。 

     顶级模块定义的方式,有两种,除了前面代码一中定义的方法,还有一种

     

     module A.B.C

     

     这种使用点号分隔的形式,它表示在当前名称空间(namespace)下定义一个名称空间A.B,并在A.B下定义模块C 

     顶级模块的定义要求除名称空间的定义或指令外的第一句。

     在F#中模块等同于c#中静态密封类。你可以使用reflector查看,在模块中定义的类型、值、函数都成为这个静态类中的静态成员 

 

 

Code

 

 

     上面的代码文件3中定义一个带有名称空间的模块,在代码文件4中,调用了3中模块定义的length函数及扩展字符串一个属性成成员Link

     这里使用了open关键字,明确要求编译器在Util.StringExtensions下查找值和类型、函数等。这样我们就可以不使用完全限定名了。在F#中open不仅可以打开名称空间,也可以打开模块。前面说过模块等同于静态类(我没有发现F#中有明确定义静态类的方法),所以模块用来放置OO中让人垢病的静态工具类,是一个理想的场所。  

     2、模块的访问限定

     模块可以使用关键字public ,internal,private来限定访问范围(模块是静态类,你可以使用原有的c#概念来推测它的行为)

     public表示公有,意味着任何程序代码都可以访问,internal表示程序集范围中都可以访问,private表示只有模块中代码可以访问 

 

 

Code

 

 

     上面代码使用public来限定范围,表示可以不受限制的访问,注意public的位置。

     在F#语言中,public,internal,private这三个关键字不仅可以用在module上来限定模块的访问范围,还可以用在let绑定上,和type上,来指定值和类型的访问边界。

     

     3、模块间引用顺序

     如果你按照这篇博客的顺序依次在VS中添加文件,你会发现一个问题,我来描述一下:

     当你添加文件一、文件二,编译执行,一切正常。

     当你添加文件三、文件四,编译执行,发现了什么问题?你的控制台上是不是一片空白,不要害怕,按一下回车吧。他打印出来了。

     当你添加文件五,编译执行,发现了什么事情?为什么控制台一闪而过,消失了。 

     

     为了继续下面的问题。我将改写文件4,来演示前面所述的模块间的访问限定。  

 

Code

 

 

     你发现了什么问题,Util. NumberExtensions没有定义,我明明定义了呀。

     好吧,我不在继续下去了,我来回答你的这些回答吧。

     回答上面的问题,需要我们思考一个问题,就是F#这种没有明确入口点的语言,依据什么来确定编译和执行的顺序。

     在C#语言中我们的控制台和window程序需要明确的告诉编译器程序入口点,需要你定义一个main方法(当然你也可以定义多个main方法,那样的话,你需要告诉编译器你使用哪 一个main方法作为入口点)。 

     F#在编译时,依赖于你传入给编译器源代码文件的顺序。在VS的msbuild文件下记载了你加入文件的顺序,vs在编译时,会使用下面的命令来编译程序。

     

     fsc.exe "a.fs" " b.fs" "stringExtension.fs" " test.fs" " numberExtensions.fs" –o "项目名.exe"

 

     这在编译test.fs时,会因为找不到Util. NumberExtensions的定义而出错。但可以通过明确告诉编译器查找的路径,来避免编译出错。  

 

 

Code

 

  

     通过#I指令来告诉fsc编译器查找路径。上面代码使用了工程项目的路径

     通过#r指令来告诉fsc编译器应在那个程序集中查找名称空间和模块  

 

     那么F#如何确定程序入口点了,答案是:你传递给编译器fsc命令的最后一个源代码文件,在本例中就是numberExtensions.fs文件,因为numberExtensions.fs文件中只定义了常量和类型定义、函数,F#编译器会生成一个空的方法。F#通过这种规定,帮助我们生成了一个_main方法,来做为程序入口点。另外,需要指出的是F#中的模块是按需加载的,这个本质上跟c#是类似的,也就是说,在入口点中未引用的模块是不会执行。这也是在加载文件三、四执行时,会执行文件三中的System.Console.ReadKey(true),而在加载文件五时,前面的所有代码都不会执行的原因。 

     刚才我们讲到F#编译器是按照传递给它文件的顺序来进行编译的,那我们只要将我们依赖的源代码文件改变顺序,同样可以解决上面的问题。 不过目前版本的F#项目服务还需要你手工完成排序操作。高兴的是下一个CTP版本为我们提供了不错的帮助。为了你能够在CTP版本未发布之前也能够使用,我将介绍如何手工改变源代码文件顺序的办法。

     (1)首先鼠标右击项目,选择卸载项目(unload project)

     (2)然后鼠标右击项目,选择编辑项目(Edit …)

     (3)在打开的项目文件中,找到下面的位置 

 

Code

 

     将numberExtensions.fs放到test.fs前面,保存。

     (4)右击项目,然后 加载项目(reload project)

 

     CTP版本中的项目服务已经可以简单的通过移上移下的方式来改变顺序了。 

 

    

      上面的代码虽然能够正常找到了Util. NumberExtensions模块了,可又出现了length函数冲突,如何解决了?我将在下篇博客中介绍命名空间和名称空间冲突的解决办法。

     F#学习之路(3)如何组织程序(下)

posted @ 2008-08-14 15:22  lvxuwen  阅读(3158)  评论(14编辑  收藏  举报