如果你也会C#,那不妨了解下F#(5):模块、与C#互相调用
F# 項(xiàng)目
在之前的幾篇文章介紹的代碼都在交互窗口(fsi.exe)里運(yùn)行,但平常開發(fā)的軟件程序可能含有大類類型和函數(shù)定義,代碼不可能都在一個文件里。下面我們來看VS里提供的F#項(xiàng)目模板。
F#項(xiàng)目模板有以下幾種類型(以VS2015為例):?
Silverlight庫創(chuàng)建Silverlight的類庫
教程模板是一個控制臺應(yīng)用程序,里面包含了F#的示例,可通過這個項(xiàng)目快速了解F#相關(guān)內(nèi)容。
“可移植庫”則可創(chuàng)建用于多平臺的庫,支持的平臺在括號里說明。
“庫”用于創(chuàng)建類庫
“控制臺應(yīng)用程序”大家就熟悉了。
安卓項(xiàng)目為安裝了Xamarin創(chuàng)建的,請忽略。
我們創(chuàng)建一個控制臺應(yīng)用程序來說明,下圖為程序的Program.fs文件及運(yùn)行結(jié)果:
我們添加一行代碼(圖中藍(lán)框中)防止運(yùn)行結(jié)束自動退出,這個應(yīng)用程序默認(rèn)是把參數(shù)打印出來,而運(yùn)行時參數(shù)為空,所以結(jié)果為一空數(shù)組([||])。
其中ignore函數(shù)用于丟棄System.Console.ReadKey()結(jié)果。
現(xiàn)在項(xiàng)目中除了AssemblyInfo.fs外,只有Program.fs一個文件,下面我們先了解模塊的相關(guān)信息再創(chuàng)建其他文件。
模塊
模塊簡介
模塊(Module)是F#程序代碼的基本組織單位。默認(rèn)情況下,每個F#代碼文件(后綴為.fs)對應(yīng)一個模塊,且必須在文件開頭指定模塊名稱。
創(chuàng)建模塊
我們創(chuàng)建File1.fs文件時,默認(rèn)會在開頭添加module?File1,當(dāng)然也可自己改成其他名稱。
module File1let x = 1在其他模塊中使用File1.x進(jìn)行訪問。
文件順序
F#項(xiàng)目中的文件是有順序要求的,在上面的文件無法訪問下面的模塊。我們可以使用Alt+上/下箭頭進(jìn)行調(diào)整文件順序,或在文件上點(diǎn)擊右鍵進(jìn)行操作:?
嵌套模塊
模塊中可嵌套模塊,但定義內(nèi)層模塊需要在模塊名后使用等號(=),且內(nèi)層模塊的內(nèi)容必須比它的上層模塊縮進(jìn)一級。
module TopLevelModel ? ? ? ?module NestedModule = ? //第一層嵌套模塊let i = 1module NestedModuleInNestedModule = ?//第二層嵌套模塊let i = 2使用模塊
若想不使用模塊名訪問模塊中的值時,則可使用open關(guān)鍵字進(jìn)行打開。但有兩個需要注意的地方:
強(qiáng)制顯示訪問
在上一章介紹的集合模塊中,我們從未使用open List或者open Seq這樣的操作。
使用F12轉(zhuǎn)到Seq的代碼定義文件可以發(fā)現(xiàn)Seq模塊使用了
[<RequireQualifiedAccess>](強(qiáng)制顯示訪問)。
附加了此特性的模塊在使用時必須使用模塊名訪問,因?yàn)閹讉€集合模塊中有大部分函數(shù)名稱是相同的,若設(shè)置此特性而可同時打開了多個模塊,則函數(shù)名稱將會沖突。
自動打開
而我們在使用printfn和ignore函數(shù)時,均不需要打開相關(guān)模塊,是因?yàn)樵谒麄兯鶎倌K附加了[<AutoOpen>](自動打開)的特性。像Operators模塊里有我們常用的運(yùn)算符,為了方便使用,添加了自動打開的特性。
我們在自定義模塊時可根據(jù)需要使用這兩個特性。
命名空間
命名空間(Namespace)和模塊類似,不同的是命名空間里不能直接定義值,只能定義類型。(與C#中的命名空間一樣,可以想象我們無法在C#的命名空間中直接定義一個方法,而需要首先定義一個類。)
但F#中的命名空間不能像模塊那樣嵌套,但可以在同一文件中定義多個命名空間。
namespace PlayingCardstype Suit = Spade | Club | Diamond | Heartnamespace PlayingCards.Pokertype PokerPlayer = {Name:string; Money:int; Position:int}上面的代碼在一個文件中使用兩個命名空間分別定義了一個類型。
其中Suit為可區(qū)分聯(lián)合(Discriminated Union)類型;PokerPlayer為記錄(Record)類型。將在下一篇介紹。
應(yīng)用程序入口
在F#中,程序從程序集的最后一個文件開始執(zhí)行,而且必須是一個模塊。但最后一個模塊的名稱可省略。
也可以使用[<EntryPoint>]特性應(yīng)用于最后一個代碼文件的最后一個函數(shù),使其成為程序入口點(diǎn)而無需顯示調(diào)用。
可查看控制臺應(yīng)用程序項(xiàng)目的模板:
let main argv = ? ? printfn "%A" argv ? ?0main函數(shù)的參數(shù)是一個數(shù)組(通常可自定義為字符串?dāng)?shù)組),是應(yīng)用程序的運(yùn)行參數(shù),返回的整數(shù)則為程序的退出代碼(exit code)。
若不使用[<EntryPoint>],則需要在最后調(diào)用該函數(shù),否則并不會自動調(diào)用該函數(shù)。
let main (argv:string[]) = printfn "%A" argvSystem.Console.ReadKey(true) |> ignore ? ?0main [||]控制臺應(yīng)用程序通常在結(jié)束之前使用System.Console.ReadKey()方法來防止運(yùn)行完成自動退出。
擴(kuò)展模塊
可以通過創(chuàng)建一個同名模塊,在其中添加值來對原有模塊進(jìn)行擴(kuò)展。
在介紹常用函數(shù)時,我們提到Seq模塊沒有提供rev函數(shù),現(xiàn)在自己實(shí)現(xiàn)以對Seq模塊進(jìn)行擴(kuò)展。
open System.Collections.Genericmodule Seq = ? ?/// 反轉(zhuǎn)Seq中的元素let rec rev (s : seq<'a>) = ? ? ? ?let stack = new Stack<'a>()s |> Seq.iter stack.Pushseq { ? ? ? ? ? ?while stack.Count > 0 doyield stack.Pop()}其中使用了.NET框架中的泛型棧集合類型(System.Collections.Generic.Stack<T>)。
與C#互相調(diào)用
F#代碼和C#代碼(包括VB.NET)一樣,都編譯成MSIL,在CLR運(yùn)行。(可參考文章《.NET框架》)所以,兩種語言之間可以方便地互相調(diào)用。
程序集的引用大家都熟悉,但C#和F#中又有一些獨(dú)立的東西不能互相使用,下面簡單介紹一下在互相調(diào)用中常見的問題。
F#調(diào)用C#代碼
本節(jié)涉及操作需要創(chuàng)建兩個項(xiàng)目,一個C#的類庫項(xiàng)目,一個F#的控制臺項(xiàng)目。然后F#項(xiàng)目引用C#項(xiàng)目。
dynamic:在F#中訪問C#的動態(tài)類型
在.NET4.0,C#引入了dynamic關(guān)鍵字使得可以像使用動態(tài)語言一樣來使用C#。但在F#中并不支持dynamic關(guān)鍵字和動態(tài)類型,在引用C#編譯的程序集時,則變成了Object類型。
我們知道dynamic在Microsoft.CSharp.dll程序集中實(shí)現(xiàn),在F#中可以通過引用此程序集,通過反射等操作自己實(shí)現(xiàn)對動態(tài)類型及屬性的訪問。
而我在平常一般使用第三方庫FSharp.Interop.Dynamic(Nuget)。代碼示例:
//C#代碼,命名空間CSharpForFSharppublic class CSharpClass{ ?public dynamic TestDynamic() ?{ ? ?return "5566";} }在F#中調(diào)用:
//F#代碼,位于F#項(xiàng)目的Program.fsopen FSharp.Interop.Dynamicopen CSharpForFSharp ? ? ? ? ? ?//C#項(xiàng)目中的命名空間let main argv = ? ? let cc = CSharpClass() ? ?let str = cc.TestDynamic() ? ?printfn "%A" (str?Length) ? //使用?替代.System.Console.Read()|>ignore ? ?0打開FSharp.Interop.Dynamic命名空間,F#中可使用?來訪問動態(tài)類型的屬性和方法。
調(diào)用帶有?ref?和?out?參數(shù)的函數(shù)
在C#中,有ref和out兩個關(guān)鍵字來修飾函數(shù)的參數(shù),使函數(shù)可以進(jìn)行引用傳遞和返回多個值。若要在F#中調(diào)用,則有一些不同。
帶有ref參數(shù)或者out參數(shù)的函數(shù),因?yàn)閰?shù)值可能在函數(shù)中發(fā)生改變,需要在F#先定義一個可變值類型,并使用尋址操作符(&)進(jìn)行傳入。
// C#代碼,位于命名空間CSharpForFSharppublic class CSharpClass{ ? ?public static bool OutRefParams(out int x, ref int y) ? ?{x = 100;y = y * y; ? ? ? ?return true;} }在F#中調(diào)用:
// F#代碼,位于F#項(xiàng)目的Program.fsopen CSharpForFSharplet mutable x,y = 0,0CSharpClass.OutRefParams(&x,&y)返回true并對x和y進(jìn)行了改變。
帶有out的參數(shù)在C#中可以使用未賦值的變量傳入,所以在F#中除了尋址傳入的方法,還可以直接忽略該參數(shù),則該函數(shù)在F#中成為了多返回值(即返回tuple)的形式:
let successful, result = Int32.TryParse(str)Int32.TryParse返回了兩個值,第一個總是函數(shù)返回值,而后是out參數(shù)。
柯里化C#的方法
因?yàn)镃#中的函數(shù)無論有多少個參數(shù),在F#中調(diào)用時都視為一個tuple參數(shù),所以無法柯里化和使用函數(shù)管道符(|>)操作。
在F#中可以使用FuncConvert類將.NET中的函數(shù)轉(zhuǎn)換成F#中的函數(shù)。
let join : string*string list -> string = System.String.Joinlet 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轉(zhuǎn)化為F#中的函數(shù),因?yàn)樵摲椒ň哂卸鄠€重載,所以第一行代碼用來指定一個要轉(zhuǎn)換的重載。
其實(shí)FuncConvert類也可以在C#中使用,需要添加FSharp.Core程序集,有興趣的可以自己嘗試。
C#調(diào)用F#代碼
本節(jié)涉及操作需要創(chuàng)建兩個項(xiàng)目,一個F#的類庫項(xiàng)目,一個C#的控制臺項(xiàng)目。然后C#項(xiàng)目引用F#項(xiàng)目,因?yàn)?strong>涉及到F#中獨(dú)有類型,還需要引用FSharp.Core程序集。
若要在UWP項(xiàng)目中引用F#項(xiàng)目,需要通過“可移植庫”模板創(chuàng)建項(xiàng)目。
因?yàn)镃#中的類型比F#少了很多,所以很多C#不支持的類型均使用類來代替,使用時只需像使用類一樣使用它就行了。而模塊,在C#中則為靜態(tài)類。
F#中的函數(shù)
需要注意的是,若在F#將函數(shù)作為參數(shù)或返回值,則F#中的函數(shù)在C#中將會變成
FSharpFunc<_,_>對象(位于FSharp.Core程序集的Microsoft.FSharp.Core命名空間)。
//F# 代碼,位于TestModule模塊open Systemtype MathUtilities = ? ?static member GetAdder() =(fun x y z -> Int32.Parse(x) + Int32.Parse(y) + Int32.Parse(z))GetAdder函數(shù)返回一個將三個字符串轉(zhuǎn)成int再相加的函數(shù),在C#中調(diào)用此函數(shù):
FSharpFunc<string, FSharpFunc<string, FSharpFunc<string, int>>> ss = MathUtilities.GetAdder();var ret = ss.Invoke("123").Invoke("45").Invoke("67");F#中的string -> string -> string -> int類型函數(shù)在C#中變成了FSharpFunc <string, FSharpFunc <string, FSharpFunc <string, int>>>。
這是因?yàn)镃#中的不支持函數(shù)柯里化,如果F#中的函數(shù)需要更多的參數(shù),在C#中調(diào)用就很麻煩了。雖然在F#使用很方便,但若需要編寫供C#使用的程序集,盡量不要使用這些功能。
命名規(guī)范
通過上面的了解,至少可以簡單地使用F#和C#互相調(diào)用。但有個地方可能使有強(qiáng)迫癥的程序員很難受:F#模塊中的函數(shù)命名使用的是駝峰式(camelCase),在C#中類的方法則使用PascalCase命名規(guī)范。
F#模塊在編譯成靜態(tài)類后,在C#中使用變得不一致。在F#中提供了CompiledName特性用來指定編譯后的名稱。
在第一篇中提到的F#中可用“`` ``”來使任何字符串作為變量(值)的名稱,若想在C#中調(diào)用這類值(不符合變量命名規(guī)則),也需要用CompiledName指定編譯后的名稱,否則無法調(diào)用。
module TestModulelet add = fun a b -> a+blet ``7?`` i = i % 7 = 0在C#中調(diào)用:
int i = TestModule.Add(3,4);var b = TestModule.IsSeven(7);相關(guān)文章:
如果你也會C#,那不妨了解下F#(1):F# 數(shù)據(jù)類型
如果你也會C#,那不妨了解下F#(2):數(shù)值運(yùn)算和流程控制語法
如果你也會C#,那不妨了解下F#(3):F#集合類型和其他核心類型
如果你也會C#,那不妨了解下F#(4):了解函數(shù)及常用函數(shù)
【送書活動】機(jī)器學(xué)習(xí)項(xiàng)目開發(fā)實(shí)戰(zhàn)
《機(jī)器學(xué)習(xí)項(xiàng)目開發(fā)實(shí)戰(zhàn)》送書活動結(jié)果公布
F#年度調(diào)查結(jié)果概述
原文地址:http://www.cnblogs.com/hjklin/p/fs-for-cs-dev-5.html
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關(guān)注
總結(jié)
以上是生活随笔為你收集整理的如果你也会C#,那不妨了解下F#(5):模块、与C#互相调用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 先定个小目标, 使用C# 开发的千万级应
- 下一篇: gRPC C#学习