如果你也会C#,那不妨了解下F#(5):模块、与C#互相调用
F# 项目
在之前的几篇文章介绍的代码都在交互窗口(fsi.exe)里运行,但平常开发的软件程序可能含有大类类型和函数定义,代码不可能都在一个文件里。下面我们来看VS里提供的F#项目模板。
F#项目模板有以下几种类型(以VS2015为例):
- Silverlight库创建Silverlight的类库
- 教程模板是一个控制台应用程序,里面包含了F#的示例,可通过这个项目快速了解F#相关内容。
- “可移植库”则可创建用于多平台的库,支持的平台在括号里说明。
- “库”用于创建类库
- “控制台应用程序”大家就熟悉了。
- 安卓项目为安装了Xamarin创建的,请忽略。
我们创建一个控制台应用程序来说明,下图为程序的Program.fs文件及运行结果:
我们添加一行代码(图中蓝框中)防止运行结束自动退出,这个应用程序默认是把参数打印出来,而运行时参数为空,所以结果为一空数组([||])。
其中ignore函数用于丢弃System.Console.ReadKey()结果。
现在项目中除了AssemblyInfo.fs外,只有Program.fs一个文件,下面我们先了解模块的相关信息再创建其他文件。
模块
模块简介
模块(Module)是F#程序代码的基本组织单位。默认情况下,每个F#代码文件(后缀为.fs)对应一个模块,且必须在文件开头指定模块名称。
创建模块
我们创建File1.fs文件时,默认会在开头添加module File1,当然也可自己改成其他名称。
module File1
let x = 1
在其他模块中使用File1.x进行访问。
文件顺序
F#项目中的文件是有顺序要求的,在上面的文件无法访问下面的模块。我们可以使用Alt+上/下箭头进行调整文件顺序,或在文件上点击右键进行操作:
嵌套模块
模块中可嵌套模块,但定义内层模块需要在模块名后使用等号(=),且内层模块的内容必须比它的上层模块缩进一级。
module TopLevelModel
module NestedModule = //第一层嵌套模块
let i = 1
module NestedModuleInNestedModule = //第二层嵌套模块
let i = 2
使用模块
若想不使用模块名访问模块中的值时,则可使用open关键字进行打开。但有两个需要注意的地方:
强制显示访问
在上一章介绍的集合模块中,我们从未使用open List或者open Seq这样的操作。
使用F12转到Seq的代码定义文件可以发现Seq模块使用了
[<RequireQualifiedAccess>](强制显示访问)。
附加了此特性的模块在使用时必须使用模块名访问,因为几个集合模块中有大部分函数名称是相同的,若设置此特性而可同时打开了多个模块,则函数名称将会冲突。
自动打开
而我们在使用printfn和ignore函数时,均不需要打开相关模块,是因为在他们所属模块附加了[<AutoOpen>](自动打开)的特性。像Operators模块里有我们常用的运算符,为了方便使用,添加了自动打开的特性。
我们在自定义模块时可根据需要使用这两个特性。
命名空间
命名空间(Namespace)和模块类似,不同的是命名空间里不能直接定义值,只能定义类型。(与C#中的命名空间一样,可以想象我们无法在C#的命名空间中直接定义一个方法,而需要首先定义一个类。)
但F#中的命名空间不能像模块那样嵌套,但可以在同一文件中定义多个命名空间。
namespace PlayingCards
type Suit = Spade | Club | Diamond | Heart
namespace PlayingCards.Poker
type PokerPlayer = {Name:string; Money:int; Position:int}
上面的代码在一个文件中使用两个命名空间分别定义了一个类型。
其中Suit为可区分联合(Discriminated Union)类型;PokerPlayer为记录(Record)类型。将在下一篇介绍。
应用程序入口
在F#中,程序从程序集的最后一个文件开始执行,而且必须是一个模块。但最后一个模块的名称可省略。
也可以使用[<EntryPoint>]特性应用于最后一个代码文件的最后一个函数,使其成为程序入口点而无需显示调用。
可查看控制台应用程序项目的模板:
[<EntryPoint>]
let main argv =
printfn "%A" argv
0
main函数的参数是一个数组(通常可自定义为字符串数组),是应用程序的运行参数,返回的整数则为程序的退出代码(exit code)。
若不使用[<EntryPoint>],则需要在最后调用该函数,否则并不会自动调用该函数。
let main (argv:string[]) =
printfn "%A" argv
System.Console.ReadKey(true) |> ignore
0
main [||]
控制台应用程序通常在结束之前使用System.Console.ReadKey()方法来防止运行完成自动退出。
扩展模块
可以通过创建一个同名模块,在其中添加值来对原有模块进行扩展。
在介绍常用函数时,我们提到Seq模块没有提供rev函数,现在自己实现以对Seq模块进行扩展。
open System.Collections.Generic
module Seq =
/// 反转Seq中的元素
let rec rev (s : seq<'a>) =
let stack = new Stack<'a>()
s |> Seq.iter stack.Push
seq {
while stack.Count > 0 do
yield stack.Pop()
}
其中使用了.NET框架中的泛型栈集合类型(System.Collections.Generic.Stack<T>)。
与C#互相调用
F#代码和C#代码(包括VB.NET)一样,都编译成MSIL,在CLR运行。(可参考文章《.NET框架》)所以,两种语言之间可以方便地互相调用。
程序集的引用大家都熟悉,但C#和F#中又有一些独立的东西不能互相使用,下面简单介绍一下在互相调用中常见的问题。
F#调用C#代码
本节涉及操作需要创建两个项目,一个C#的类库项目,一个F#的控制台项目。然后F#项目引用C#项目。
dynamic:在F#中访问C#的动态类型
在.NET4.0,C#引入了dynamic关键字使得可以像使用动态语言一样来使用C#。但在F#中并不支持dynamic关键字和动态类型,在引用C#编译的程序集时,则变成了Object类型。
我们知道dynamic在Microsoft.CSharp.dll程序集中实现,在F#中可以通过引用此程序集,通过反射等操作自己实现对动态类型及属性的访问。
而我在平常一般使用第三方库FSharp.Interop.Dynamic(Nuget)。代码示例:
//C#代码,命名空间CSharpForFSharp
public class CSharpClass
{
public dynamic TestDynamic()
{
return "5566";
}
}
在F#中调用:
//F#代码,位于F#项目的Program.fs
open FSharp.Interop.Dynamic
open CSharpForFSharp //C#项目中的命名空间
[<EntryPoint>]
let main argv =
let cc = CSharpClass()
let str = cc.TestDynamic()
printfn "%A" (str?Length) //使用?替代.
System.Console.Read()|>ignore
0
打开FSharp.Interop.Dynamic命名空间,F#中可使用?来访问动态类型的属性和方法。
调用带有 ref 和 out 参数的函数
在C#中,有ref和out两个关键字来修饰函数的参数,使函数可以进行引用传递和返回多个值。若要在F#中调用,则有一些不同。
带有ref参数或者out参数的函数,因为参数值可能在函数中发生改变,需要在F#先定义一个可变值类型,并使用寻址操作符(&)进行传入。
// C#代码,位于命名空间CSharpForFSharp
public class CSharpClass
{
public static bool OutRefParams(out int x, ref int y)
{
x = 100;
y = y * y;
return true;
}
}
在F#中调用:
// F#代码,位于F#项目的Program.fs
open CSharpForFSharp
let mutable x,y = 0,0
CSharpClass.OutRefParams(&x,&y)
返回true并对x和y进行了改变。
带有out的参数在C#中可以使用未赋值的变量传入,所以在F#中除了寻址传入的方法,还可以直接忽略该参数,则该函数在F#中成为了多返回值(即返回tuple)的形式:
let successful, result = Int32.TryParse(str)
Int32.TryParse返回了两个值,第一个总是函数返回值,而后是out参数。
柯里化C#的方法
因为C#中的函数无论有多少个参数,在F#中调用时都视为一个tuple参数,所以无法柯里化和使用函数管道符(|>)操作。
在F#中可以使用FuncConvert类将.NET中的函数转换成F#中的函数。
let join : string*string list -> string = System.String.Join
let curryJoin = FuncConvert.FuncFromTupled join
[ 1..10 ]
|> List.map string
|> curryJoin "*" // "1*2*3*4*5*6*7*8*9*10"
let joinStar = curryJoin "*" // joinStar类型为:string list -> string
以上代码将System.String.Join转化为F#中的函数,因为该方法具有多个重载,所以第一行代码用来指定一个要转换的重载。
其实FuncConvert类也可以在C#中使用,需要添加FSharp.Core程序集,有兴趣的可以自己尝试。
C#调用F#代码
本节涉及操作需要创建两个项目,一个F#的类库项目,一个C#的控制台项目。然后C#项目引用F#项目,因为涉及到F#中独有类型,还需要引用FSharp.Core程序集。
若要在UWP项目中引用F#项目,需要通过“可移植库”模板创建项目。
因为C#中的类型比F#少了很多,所以很多C#不支持的类型均使用类来代替,使用时只需像使用类一样使用它就行了。而模块,在C#中则为静态类。
F#中的函数
需要注意的是,若在F#将函数作为参数或返回值,则F#中的函数在C#中将会变成
FSharpFunc<_,_>对象(位于FSharp.Core程序集的Microsoft.FSharp.Core命名空间)。
//F# 代码,位于TestModule模块
open System
type MathUtilities =
static member GetAdder() =
(fun x y z -> Int32.Parse(x) + Int32.Parse(y) + Int32.Parse(z))
GetAdder函数返回一个将三个字符串转成int再相加的函数,在C#中调用此函数:
FSharpFunc<string, FSharpFunc<string, FSharpFunc<string, int>>> ss = MathUtilities.GetAdder();
var ret = ss.Invoke("123").Invoke("45").Invoke("67");
F#中的string -> string -> string -> int类型函数在C#中变成了FSharpFunc <string, FSharpFunc <string, FSharpFunc <string, int>>>。
这是因为C#中的不支持函数柯里化,如果F#中的函数需要更多的参数,在C#中调用就很麻烦了。虽然在F#使用很方便,但若需要编写供C#使用的程序集,尽量不要使用这些功能。
命名规范
通过上面的了解,至少可以简单地使用F#和C#互相调用。但有个地方可能使有强迫症的程序员很难受:F#模块中的函数命名使用的是驼峰式(camelCase),在C#中类的方法则使用PascalCase命名规范。
F#模块在编译成静态类后,在C#中使用变得不一致。在F#中提供了CompiledName特性用来指定编译后的名称。
在第一篇中提到的F#中可用“`` ``”来使任何字符串作为变量(值)的名称,若想在C#中调用这类值(不符合变量命名规则),也需要用CompiledName指定编译后的名称,否则无法调用。
module TestModule
[<CompiledName("Add")>]
let add = fun a b -> a+b
[<CompiledName("IsSeven")>]
let ``7?`` i = i % 7 = 0
在C#中调用:
int i = TestModule.Add(3,4);
var b = TestModule.IsSeven(7);
本文发表于博客园。 转载请注明源链接:http://www.cnblogs.com/hjklin/p/fs-for-cs-dev-5.html。
如果你也会C#,那不妨了解下F#(5):模块、与C#互相调用的更多相关文章
- 如果你也会C#,那不妨了解下F#(7):面向对象编程之继承、接口和泛型
前言 面向对象三大基本特性:封装.继承.多态.上一篇中介绍了类的定义,下面就了解下F#中继承和多态的使用吧.
- 如果你也会C#,那不妨了解下F#(4):了解函数及常用函数
函数式编程其实就是按照数学上的函数运算思想来实现计算机上的运算.虽然我们不需要深入了解数学函数的知识,但应该清楚函数式编程的基础是来自于数学. 例如数学函数\(f(x) = x^2+x\),并没有指定 ...
- 如果你也会C#,那不妨了解下F#(6):面向对象编程之“类”
前言 面向对象的思想已经非常成熟,而使用C#的程序员对面向对象也是非常熟悉,所以我就不对面向对象进行介绍了,在这篇文章中将只会介绍面向对象在F#中的使用. F#是支持面向对象的函数式编程语言,所以你用 ...
- 如果你也会C#,那不妨了解下F#(3):F#集合类型和其他核心类型
本文链接:http://www.cnblogs.com/hjklin/p/fs-for-cs-dev-3.html 在第一篇中,我们介绍了一些基础数据类型,其实那篇标题中不应该含有"F#&q ...
- 如果你也会C#,那不妨了解下F#(1):F# 数据类型
本文链接:http://www.cnblogs.com/hjklin/p/fs-for-cs-dev-1.html 简单介绍 F#(与C#一样,念作"F Sharp")是一种基于. ...
- 如果你也会C#,那不妨了解下F#(2):数值运算和流程控制语法
本文链接:http://www.cnblogs.com/hjklin/p/fs-for-cs-dev-2.html 一些废话 一门语言火不火,与语言本身并没太大关系,主要看语言的推广. 推广得好,用的 ...
- python下的select模块使用 以及epoll与select、poll的区别
python下的select模块使用 以及epoll与select.poll的区别 先说epoll与select.poll的区别(总结) 整理http://www.zhihu.com/question ...
- windows下安装python模块
如何在windows下安装python模块 1. 官网下载安装包,比如(pip : https://pypi.python.org/pypi/pip#downloads) pip-9.0.1.tar. ...
- Linux下安装opencv模块
最近微信上流行的给自己的头像加一顶圣诞帽,想用python写一个程序自己实现一下,其中需要用到opencv import cv2 现在记录一下如何在Linux系统(ubutun)下安装该模块: 参考了 ...
随机推荐
- 怎么让网站在本地支持SSL?
打开vs,点击项目,查看属性,打开ssl 如果有什么危险提示,就允许 右击项目,选择属性 运行项目
- hash表长度优化证明
hash表冲突的解决方法一般有两个方向: 一个是倾向于空间换时间,使用向量加链表可以最大程度的在节省空间的前提下解决冲突. 另外一个倾向于时间换空间,下面是关于这种思路的一种合适表长度的证明过程: 这 ...
- 【C#附源码】数据库文档生成工具支持(Excel+Html)
[2015] 很多时候,我们在生成数据库文档时,使用某些工具,可效果总不理想,不是内容不详细,就是表现效果一般般.很多还是word.html的.看着真是别扭.本人习惯用Excel,所以闲暇时,就简单的 ...
- Java程序员:工作还是游戏,是该好好衡量一下了
前阵子我终于下定决心,删掉了硬盘里所有的游戏. 身为一个程序猿,每天都要和各种新技术打交道,闲暇时间,总还得看一下各大论坛,逛逛博客园啥的,给自己充充电.游戏的话,其实我自小就比较喜欢,可以算是一种兴 ...
- 漫谈TCP
不得不承认,tcp是一个非常复杂的协议.它包含了RFC793及之后的一些协议.能把tcp的所有方面面面具到地说清楚,本身就是个很复杂的事情.如果再讲得枯燥,那么就会更让人昏昏欲睡了.本文希望能尽量用稍 ...
- UVa 122 Trees on the level
题目的意思: 输入很多个节点,包括路径和数值,但是不一定这些全部可以构成一棵树,问题就是判断所给的能否构成一棵树,且没有多余. 网上其他大神已经给出了题目意思:比如我一直很喜欢的小白菜又菜的博客 说一 ...
- PHP设计模式(四)单例模式(Singleton For PHP)
今天讲单例设计模式,这种设计模式和工厂模式一样,用的非常非常多,同时单例模式比较容易的一种设计模式. 一.什么是单例设计模式 单例模式,也叫单子模式,是一种常用的软件设计模式.在应用这个模式时,单例对 ...
- AndroidStudio — Error:Failed to resolve: junit:junit:4.12错误解决
原博客:http://blog.csdn.net/u013443865/article/details/50243193 最近使用AndroidStudio出现以下问题: 解决:打开app下的buil ...
- Atitit.技术管理者要不要自己做开发??
Atitit.技术管理者要不要自己做开发?? 1. 为什么很多管理者不能自己亲自做了1 1.1. 沟通成本多了1 1.2. .组织分散. 1 1.3. 会议多 .协调多 1 1.4. 问题的根源在于我 ...
- Mysql - 触发器/视图
触发器在之前的项目中, 应用的着实不多, 没有办法的时候, 才会去用这个. 因为这个东西在后期并不怎么好维护, 也容易造成紊乱. 我最近的项目中, 由于数据库设计(别人设计的)原因, 导致一些最简单功 ...