F 探險之旅(二) 函式式程式設計(中)

2021-05-01 12:30:20 字數 4760 閱讀 1589

操作符(operator)

f#中,可把操作符看作一種函式呼叫的更為優雅的方式。操作符有兩種:字首(prefix)和中綴(infix),前者接受乙個運算元(operand),出現在運算元之前;後者接受兩個或多個運算元,出現在頭兩個運算元之間。

f#提供了豐富的操作符集,可用於數字、布林值、字串和集合型別。這些操作符數量甚眾,限於篇幅,在此不再一一詳解。本文將著重介紹如何使用和定義操作符。

類似於c#,f#的操作符也可以過載,也就是說,我們可以將不同的型別用於同一操作符,如「+」;但是與c#不同的是,各個運算元必須為相同的型別。f#的操作符過載規則與c#類似,因此任何bcl或者使用c#編寫的.net類庫中的支援操作符過載的類在f#中一樣支援過載。

letwords="tolive"+"is"+"tofunction."

opensystem

letoneyearlater=datetime.now+newtimespan(365,0,0,0,0)

我們可以定義自己的操作符,也可以重定義已有的任何操作符(不建議這樣做)。看看下面這種不好的做法:

let(+)ab=a–b

print_int(1+2)

看到這裡,你想到了什麼?是不是:這分明是在定義乙個函式嘛!所以我們前面說「可把操作符看作一種函式呼叫的更為優雅的方式」。我們重定義了「+」操作符,所以「1 + 2」的結果為-1,這當然沒什麼好處,在vs中使用fsi時,怎樣把「+」改回它原本的含義呢?我一般在任務管理器中把fsi程序關掉,再按回車,「+」就回來了。

自定義的操作符不能包含字母和數字,可以使用的字元如下:

!$%&+-./<=>?@^|~

操作符的第乙個字元可以是上面第一行的任意字元,其後的字元則可以是上面的任意字元了。定義語法與函式類似,除了要將操作括起來。看下面的例子:

let(+^*)ab=(a+b)*(a*b)

結果為30。

列表(lists)

列表是內置於f#的簡單集合型別。可以是乙個空表(empty list),使用方括號表示()。我們可以將乙個值與列表連線,此時要使用「::」操作符,注意要將值作為第乙個運算元:

letemptylist=

letoneitem="one"::

lettwoitem="two"::oneitem

在vs中可以看到oneitem的型別為string list。如果列表包含多個項,用上面的方法顯得麻煩了,我們可以使用下面的語法:

letshorthand=["hello";"world!"]

另外我們還可使用「@」操作符來連線兩個列表:

letconcatenatelists=["one,";"two,"]@["three,";"four"]

f#要求列表中的元素型別必須是相同的,如果你確實需要列表包含不同型別的元素,那只好建立乙個obj(即system.object)型別的列表了:

letobjlist=[box1;box2.0;box"three"]

其中第三個box是可選的,這個讓我想起了c#中的裝箱。

f#中的列表是不可修改的,一旦建立就不能修改了。作用於列表的函式和操作符也不能修改列表,而是建立了列表的乙個副本。這個特性很像c#中的string型別。看下面的例子:

#light

letprintlistlist=

list.iterprint_stringlist

print_newline()

letthreeitems=["one";"two";"three"]

letreversedlist=list.revthreeitems

printlistthreeitems

printlistreversedlist

上面的iter方法接受兩個引數,第乙個是函式,第二個是列表,其作用是將函式依次應用於列表的每個元素,有點像c#中的foreach迴圈。而rev方法則返回列表的逆序列表。

列印結果為:

one tow three

three two one

列表推導(list comprehensions)

最簡單的情況是指定列表的範圍,如:

letnumericlist=[0..9]

letcharlist=['a'..'z']

這兩個列表的型別分別是int list和char list,範圍分別是從0到9和從』a』到』z』。

更複雜的情況是指定乙個步長:

letmultipleofthree=[0..3..30]

letrevnumericlist=[9..-1..0]

第乙個列表的值是0到30間所有3的倍數,第二個列表的元素則包含了從9遞減至0。

我們還可以通過對乙個列表進行迴圈操作得到另乙個列表。例如:

letsquares=[forxin1..10->x*x]

通過for進行迴圈,squares列表的元素是1到10間的整數的平方。

此外還可以為迴圈新增when子句對元素進行過濾,只有when子句的值為true時才對其進行運算:

letevens=[forxin1..10whenx%2=0->x]

evens的元素為[2; 4; 6; 8; 10]。

控制流程(control flow)

f#擁有強的控制流程概念,這與很多純函式式程式語言不同,在這些語言中表示式可以以任何順序進行求值。看下面的例子:

letabsolutevaluex=

ifx<0then

-xelifx=0then

0else

x

if, elif, then, else組成的結構我們應當很熟悉,在f#中該結構是乙個表示式,也就是說它需要返回乙個值。而且每個分支返回的值應當具有相同的型別,否則就會有編譯錯誤。如果確實要返回多個型別的值,在值前加box關鍵字,就像前面建立列表時那樣,這樣表示式的返回型別為obj。

型別與型別推導(types and type inference)

f#是一種強型別的語言,傳給函式的值必須是指定的型別。如果函式接受string型別的引數,就不能傳給它int型別的值。一種語言處理其中值的型別的方式稱為語言的型別系統。f#的型別系統與一般語言不同,包括函式在內,所有的值都具有自己的型別。

通常情況下,我們不需要顯式地宣告型別,編譯器會嘗試從值的文字值或呼叫的函式返回型別來判斷其型別,這個過程稱為型別推導。可在編譯時使用-i開關來顯示所有的推導型別,在vs中我們則可以使用工具提示來檢視識別符號的型別。先看下面值的型別推導情況:

letstrvalue="stringvalue"

letintvalue=12

在fsi中可看到它們的資訊是:

valstrvalue:string

valintvalue:int

可以理解,編譯器跟據賦給識別符號的文字值來推導其型別。再看看下面函式的情況:

letmakemessagex=(string_of_boolx)+"isabooleanvalue"

lethalfx=x/2

在fsi中可看到它們的資訊是:

valmakemessage:bool->string

valhalf:int->int

有意思的是,函式名前面也有個val,這表明函式也是值,後面的bool -> string是什麼意思呢?它表明函式接受bool型別引數,返回string型別的值,注意x作為string_of_bool的引數,所以x必須為bool型別,返回值是兩個字串相加的值,故返回值也是string型別。對於half函式,單從定義不能確定x型別,此時編譯器採用預設的型別int。再看看稍微複雜點的情況:

letdiv1xy=x/y

letdiv2(x,y)=x/y

這兩個函式的資訊是:

valdiv1:int->int->int

valdiv2:int*int->int

div1函式可接受部分引數(可柯里化),而div2則必須同時傳入兩個int型別的值。考慮下面的函式:

letdonothingx=x

其資訊為:

valdonothing:'a->'a

a』 -> a』表示函式接受任意型別,並返回與其相同型別的值。以』打頭的型別表示可變型別(variable type),編譯器雖然不能確定型別的引數,卻能確定返回值型別必須與引數型別相同,型別系統的這種特性稱為型別引數化,通過它編譯器也能發現更多的型別錯誤。可變型別或型別引數化的概念,類似於.net 2.0的泛型,如果f#基於支援泛型的cli,那麼它會充分利用泛型的優勢。另外,f#的建立者don syme,正是clr中泛型的設計者和實現者。

f#的型別推導固然強大,但它顯然不能揣測出開發人員所有的心思來,如果有特殊需求該怎麼辦呢?看下面的例子:

letdonothingtofloat(x:float32)=x

float32即system.single,這裡我們手動指定了x的型別,這個有時稱為型別標註(type annotation)。如果要在f#中使用其它.net語言編寫的類庫,或者與非託管的類庫進行互操作,它會很有用。

小結參考:

《foundations of f#》 by robert pickering

《f# specs》

函式式程式語言F

文 高昂 作為微軟支援的第乙個函式式語言,f 在專案中被越來越多的開發者選用,8月的tiobe排行榜,f 挺進前二十。源於微軟研究院的f 語言因其優良的設計和強大的並行程式設計能力,正得到越來越多.net開發者的選用。在8月的tiobe語言流行度排行榜中,f 語言首次進入了前二十位。f 是微軟.ne...

F 程式設計 函式式程式設計之Records

當你想把資料組成乙個結構化的格式,而不需要太複雜的語法時,你可以使用f 中的record型別。record型別與c語言的struct型別基本一樣,儲存一組型別的值,通過欄位的值來獲取。定義乙個record型別很簡單,只需要在大括號內定義系列的名稱 型別就可以。要例項化乙個record,只需要提供對應...

函式式程式設計之 初窺F

大量講解函式式程式語言的書籍最終都會用fuctor,monad,monoids,範疇論等各種詞彙嚇退命令式語言玩家,所以我試圖避開這些問題,揭開這些複雜詞彙帶來的具有實戰意義的成果。另外我會盡量使用c 語言來描述函式式程式設計思想,因為c 某些語法和特性來自於函式式語言的啟發,但c 終究並不是正統的...