F#探险之旅(六):F#代码的组织

简介:

前言

是的,我们已经学习了如何在F#中使用各种范式(函数式、命令式、面向对象)进行编程。但是目前还仅限于在单个模块内编写,要知道,不管是采用哪种语言或者范式编程,如果项目规模大了,都不适合把所有代码放在单个模块内。

在常规的.NET项目中(比如C#+ASP.NET),我们往往会选择使用Solution的概念作为整个(独立)问题域的解决方案,Solution以下则是Project、File。这些概念在物理上往往表现为程序集(类库或可执行程序)、类文件等,如果项目和文件数量较多,就该好好考虑如何在组织它们。下面从这三个层次上分别来讨论一下。

Solution层次 

这里主要考虑的是Project之间的相互关系,此时基本上我们可以忽略语言的不同,也可以说在这个层次上,语言的影响不大。所以说我们把那些在用C#开发时采用的代码组织原则搬过来用。比如Martin Fowler在《企业应用架构模式》中谈到的内容,比如Robert Martin在《敏捷软件开发》中提到的关于包的设计原则,还包括.NET社区中关于PetShop架构的讨论等等,都可以加以借鉴。关于这方面的内容已有大量相关的讨论,在此不再赘述。

这里只谈一个具体的问题:如何添加对其它程序集的引用。在F# CTP 1.9.6.0之前,添加对程序集的引用需要#I和#r指令,#I用来指定要引用的程序集的目录,#r则用来指定要引用程序集的路径(包含文件名,可以是相对路径或绝对路径)。这两个指令既可以放在代码文件中,也可以放在编译选项中。其中有个小窍门,注册表中.NETFramework节点下包含了各.NET版本的一些信息,其中的AssemblyFoldersEx中有若干个目录信息,如果程序集所在目录出现在AssemblyFoldersEx中,就可以直接使用#r和文件名来添加引用了。

在CTP版本中,可以像常规的C#/VB.NET项目中那样,为项目添加对其它程序集的引用(包括引用同一解决方案中的其它项目):
 

而#r只能用于fsx脚本文件或者放在编译选项中。

Project层次 

现在假定你已经对上述设计原则有了足够的了解,并运用这些原则完成了设计,下一步就是如何使用F#来实现这些设计。现在我们进入到了Project这个层次,需要考虑Project中各代码实体之间的关系,这些实体可以是物理上的源码文件,也可以是逻辑上的模块、类型、配置等。F#中最基本的组织结构是命名空间和模块,命名空间的概念与C#中的一样。借助于Reflector可以看到模块在编译之后就是静态类,我们在为模块添加成员时要了解,这是在向一个静态类添加成员。关于命名空间和模块的相关知识,强烈推荐Lvxuwen的如何组织程序(

File层次 

现在考虑源码文件内部的基本问题。在使用函数式编程范式时,除了模块,还可以采用F#的自定义类型,F#中的类型分为两类,一是元组(Tuple)或记录(Record)类型,它们类似于C#中的类;二是Union类型,有时又称为Sum类型。通过Reflector可以看到,元组值是Tuple类型的实例,而Tuple实现了 Microsoft.FSharp.Core.IStructuralHash和System.IComparable接口;记录和Union则直接实现了这两个接口。要了解IStructualHash接口的更多内容,请参考Jome Fisher的文章。

而在使用面向对象编程范式时,我们可以像在C#中那样定义.NET类型,比如接口、类、结构、枚举、委托等等。当然这其中的编程细节比较多(建议看看我前面写过的几篇随笔),而且对于同一问题可以采取不同的方案。这需要我们去多多学习和实战,根据不同的需要作出选择。

这里来看另一个具体的问题:如何使用F#中的签名文件(Signature file)。在学习C语言时,接触过函数原型的概念,它给出了函数的名称、参数类型和返回类型,函数签名的含义与函数原型是一样的。如果我们把模块内的函数签名抽取出来,放在单独的一个文件中,这就是签名文件的由来。它的作用在于,它可以控制模块内函数的访问修饰符。如果要使用签名文件,那么它必须与其控制的模块文件成对出现,并且文件名相同。比如:

复制代码
F# Code - myModule.fsi
#light
module FsLib.MyModule

/// 获取一个浮点数的平方值
val square: float -> float

/// 获取一个浮点数的立方值
val cube: float -> float
复制代码

 

复制代码
F# Code - myModule.fs
#light
module FsLib.MyModule

open System

let pow x y = Math.Pow(x, y)

let square x = pow x 2.0
let cube x = pow x 3.0
复制代码


*.fsi即签名文件,这里定义了两个函数的签名:square和cube。*.fs即实现文件,它必须要提供对应的签名文件的所有函数的实现。其它程序集的模块,只能访问*.fsi中具有签名的函数。通过Reflector可以看到,对于myModule.fs中的三个函数,square和cube的修饰符为public,而pow则为internal

由此看来签名文件的作用很像C#中的接口。但事实上,编译后并没有真正生成接口。需要注意的是,如果要为代码添加XML文档注释,需要加在签名文件(如果模块有的话)而不是模块中。下面来看看如何在代码中添加注释。

常规注释 

在F#中,单行注释使用//,而多行注释则使用(* … *)。

XML文档注释 

如果为代码添加了文档注释,可以在编译时生成XML文档,然后借助于一些工具(如SandCastle)就可以生成容易使用的帮助文档。在上面的代码中可以看到,直接使用///可以为模块或其成员添加文档注释,这个要比C#中简便一些。同时也完全可以使用C#中那样完整的文档注释格式(比如使用Summary、Param等节点)。

最后,如果要在F#使用C#类库中的代码,可以参考前面写过的一篇随笔:F#命令式编程,了解关于这方面的内容。

F#的Project可以编译为类库或可执行应用程序(控制台应用程序或Windows应用程序)。我打算在后面的随笔就这两方面展开讨论,并尝试一些有实战意义的小型项目,相信到那时对代码组织的认识会更为准确。

小结 

在初学F#时,我们可以很随便地将代码放在同一模块内做些尝试或者测试。但我们程序员不该是随便的人,随着项目规模的增大,代码的组织问题会变得越发重要,我们应当越加重视。在VS中进行开发时, 整个项目的组织自然地分为了Solution、Project、File三个层次,本文在这三个层次上就代码组织的基本问题做了讨论,写得比较简单,欢迎您来留言讨论 。

(要了解本人所写的其它F#随笔请查看 F#系列随笔索引

参考 
《Foundations of F#》 by Robert Pickering
Detailed Release Notes for the F# Sep 2008 CTP release
园子里Lvxuwen的如何组织程序(
F#先锋维坦F# CTP 1.9.6.0 发行说明(Release Notes)摘要


本文转自一个程序员的自省博客园博客,原文链接:http://www.cnblogs.com/anderslly/archive/2008/11/05/fsharp-adventure-code-organization.html,如需转载请自行联系原作者。

目录
相关文章
|
2月前
|
人工智能 搜索推荐 机器人
GEO优化的诊断与调优:解析AI时代内容策略
本文系统解析生成式引擎优化(GEO)——AI时代SEO的演进形态,聚焦内容在AI理解、信任与重构中的新挑战。依托于磊老师首创的“两大核心+四轮驱动”方法论,提供诊断盲区、量化评估与实操调整的完整路径,助力内容真正被AI看见、信任并引用。
176 0
|
机器学习/深度学习 Web App开发 数据可视化
训练可视化工具哪款是你的菜?MMCV一行代码随你挑
在深度学习中可视化模型的训练过程有助于我们分析模型的状态。可视化训练过程的库很多,我们将一些常用的库集成到 MMCV 中方便用户使用。在 MMCV 中使用这些库只需简单配置。在本文中将介绍这些库以及它们在 MMCV 中的使用方法。
1390 0
训练可视化工具哪款是你的菜?MMCV一行代码随你挑
|
数据可视化 测试技术 Go
Go 语言测试与调试:`go test` 工具用法
`go test` 是 Go 语言内置的测试工具,支持单元测试、基准测试、示例测试等功能。本文详解其常用参数、调试技巧及性能测试命令,并提供实际项目中的应用示例与最佳实践。
|
前端开发 UED
🌟前端分页加载/懒加载预览PDF🌟
🌟前端分页加载/懒加载预览PDF🌟
1190 0
|
机器学习/深度学习 安全 数据安全/隐私保护
Jupyter Notebook本地部署并实现公网远程访问内网Jupyter服务器【内网穿透】
Jupyter Notebook本地部署并实现公网远程访问内网Jupyter服务器【内网穿透】
2419 0
|
测试技术 API 开发者
.NET单元测试框架大比拼:MSTest、xUnit与NUnit的实战较量与选择指南
【8月更文挑战第28天】单元测试是软件开发中不可或缺的一环,它能够确保代码的质量和稳定性。在.NET生态系统中,MSTest、xUnit和NUnit是最为流行的单元测试框架。本文将对这三种测试框架进行全面解析,并通过示例代码展示它们的基本用法和特点。
2377 8
|
IDE Java 应用服务中间件
如何检查并解决类路径中的类库版本冲突问题
类路径中的类库版本冲突可能导致应用运行异常。解决方法包括:1. 使用依赖管理工具(如Maven、Gradle)检查依赖树,找出冲突的库;2. 调整依赖版本或排除特定版本;3. 清理缓存,重新构建项目。
923 2
|
监控 安全 网络安全
探索零信任安全架构:一种现代网络安全策略
零信任安全架构是一种旨在应对现代复杂网络威胁的新型网络安全策略。它的核心理念是“永不信任,始终验证”,即无论内部还是外部的访问请求都需要经过严格的身份验证和授权。在本文中,我们将深入探讨零信任安全架构的基本概念、主要组件及其在实际应用中的优势和挑战。
793 27
|
索引 Python
Python 教程之 Pandas(8)—— 在 Pandas 中处理缺失数据
Python 教程之 Pandas(8)—— 在 Pandas 中处理缺失数据
881 0
|
C# 前端开发
WPF DatePicker默认显示当前日期,格式化为年月日
原文:WPF DatePicker默认显示当前日期 WPF的日历选择控件默认为当前日期,共有两种方法,一种静态,一种动态。 静态的当然写在DatePicker控件的属性里了,动态的写在对应的cs文件里,具体请看下面。
7075 0