F#学习之路(2) 深刻理解函数(上)
?????函數在函數式編程語言中是一等公民,是函數式語言中最重要的基本組成元素,也是其名稱的由來。
?????F# 中的函數之如C#中的類,是組織程序結構的最基本單元。是命令式編程語言中函數或OO編程語言中方法的超集。超集,有多強大?我將在下面幾個方面細細道來。
?????F#是一種多范式的編程語言。支持命令式、函數式、面向對象的編程范式,還有目前火熱的面向語言編程(DSL)。本文不會介紹其他的編程范式,只介紹函數式編程范式。
?????在面向對象編程的世界里,視命令式或過程式是一種丑陋的編程方式,至少被大多數程序員視為不能有效發揮OO語言強大功效的編程方法。在函數式編程世界里,同樣會認為命令式、OO的編程范式只是輔助的編程方法。過程式的流程控制語句被認為是不適宜的,if else 的條件判斷被替代為模范匹配,while,for的循環語句也替代為使用尾遞歸的遞歸函數。盡管F#支持其他兩種編程范式,但還是應限定在特定的場景下,應主要以函數式編程范式為主,我將在以后專門寫一篇blog來探討F#中各種編程范式相應的適用場合。
?????本文假設已經使用了#light指令,下面的代碼風格都是輕量的。注意#light指令要求你嚴格的縮進,空格被視為語法的一部分,同一層級的語句之間必須嚴格的對齊。為了方便的縮進,請設置tab為插入空格代替,我在前文 《F#學習之路 (1)什么是函數式編程》已有介紹設置的方法。
?????1、如何定義一個普通函數
?????F#中函數分為遞歸函數和非遞歸函數,我把非遞歸函數稱為普通函數。F#中函數默認是不能遞歸的。
?????代碼一:
?????
Code?1?????let?f0?()?=25?
?2
?3?????let?f1?x?=x+1?
?4
?5?????let?f11?_?=1/0?
?6
?7?????let?f2?x?y=x+y?
?8
?9?????let?f3?(x:float)?(y:float)?(z:float):string?=sprintf?"%f"?(?x*y*z)?
10
11?????let?isEven?=fun?x?->?x?%2=0?
12
13?????let?fn?f?x=?
14
15??????????match?isEven?x?with?
16
17??????????|?true?->?f(x)?
18
19??????????|?false?->()?
20
21?????let?print_even?x=?fn?(?fun?x?->?printfn?"%d?is?even"?x)?x?
22
23?????print_even?20?
24
25
???
?????上面的代碼,你能夠全部讀懂嗎?。
?????在F#中一切皆是表達式,記得這一點非常重要。這不同于命令式編程范式有語句概念。
?????什么是表達式,從學習c語言開始,給我灌輸的概念是,表達式由運算符、常量、變量組成,表達運算產生結果值。我仍沿用這個概念,不過這個運算符、常量、變量有了新變化 。運算符是函數,大多數運算符允許你重新定義。默認定義的標識符是常量,不可變,變量使用mutable關鍵字指定。函數是表達式,lambda(可理解為匿名函數)是表達式,就連過程式的if else也是表達式,有運算結果值。需要指出的是在F#中,函數的返回值是由最后一個表達式計算的結果值,并且不能使用return 關鍵字指定。在循環結構while,for中也不能使用return。break目前版本用做保留關鍵字,return只能用做工作流中(一種使用monad算子的語法糖,我將在后面簡述),while,for 表達式的結果值為(),()為Unit類型的實例,相當于C系列的void。因為沒有了return,break,在F#中過程式的代碼控制能力是受限的,要編寫break,return的語義,你需要借助閉包和遞歸函數,所以你還是轉變一下思維吧。
??????????代碼二:
??????????
Code1?let?unit=print_even?20?
2?
3?printfn?"%A"?unit?
4?
5?let?value=if?3/2=1?then?true?else?false;?
6?
7?printfn?"%A"?value?
8?
9?
?
?????上面這段代碼演示了如何接受unit結果值和if else表達式返回的結果值?
?????讓我們回到正題。我來解釋一下代碼一的片段。
????
?????let f0 () =25
?????f0是函數名稱,他帶有一個參數,參數類型為Unit類型,返回值為整型25 。函數簽名或類型為 val f0:unit->int。
?????F#是一種強類型的語言,是嚴格的函數式語言,而不是惰性函數語言,盡管他提供了一些有效的方法來支持惰性。是強類型的語言,那為什么沒有類型聲明了。
F#來自于ML家族,其語法規則主要借鑒OCmal,他使用類型推斷技術,根據上下文來推斷類型信息。
?
?????let f1 x =x+1
?????函數f1,他帶有一個參數x,參數類型為int型,返回值為整型。函數簽名為val f1:int->int。F#是如何推斷出函數的參數類型和返回值類型的,在F#中 (+)操作符,其操作數1為整數,所以要求x只能是整型,(+)函數的返回值為整數,所以可以推斷出f1的類型。
?????注意的是f0,f1的參數書寫,在c#中方法的參數是需要括號的,而在F#的括號是可以省略的,更需要指出的是f0的()不能看成是括號,而是Unit類型的實例。有意思的是在目前的版本中unit 的值(),左括號與右括號之間可以有任意的空格。這不符合人們的認知。因為實例標識應是固定的,這也是造成人們視()為符號括號的主要原因。上面的f1可以寫作 let f1 (x) =x+1 ,因為在F#中括號可以省略,所以官方給出的代碼大都省略了。
?????let f11 _ =1/0
?????這段代碼跟f1的定義,有兩點需要指出。一個就是參數使用了 下劃線 _,這表示參數可以使用任何類型。二是函數體中的1/0,這在c#中無法編譯通過的,而在F#中(/)運算符是函數,所以只會在運行時出現除零錯。F11的函數簽名為val f11:'a->int,'a表示這是一個泛型類型,因為沒有約束,所以可以接受任何類型
?
?????let f2 x y=x+y
?????F#中多個參數之間使用空格分隔,而不是使用逗號。這個函數簽名為val f2:int->int->int,表示有兩個int型參數,返回值為int型。
?????注意這個定義 :let f22 (x,y)=x+y
?????f2與f22有很大的區別,f22表示的是一個參數,其類型為元組(tuple)類型,返回值為int型,在F#中元組類型使用(x,y,z)方式定義。元組表示 一組類型值的集合,可為相同的類型也可為不同的類型,元組是F#的原生類型,也意味著函數的返回值可以返回一個元組類型的值,方便我們返回多值。f22的函數簽名為val f22:int*int->int。
?
?????let f3 (x:float) (y:float) (z:float):string =sprintf "%f" ( x*y*z)
?????函數f3有三個參數,都是浮點型,c#的double類型,返回值為string,函數簽名為 val:float->float->float->string,f3的函數定義使用顯示指定類型,F#的( * )操作符默認同樣是操作int型的,所以通過顯示指定的方法,強制f#編譯器使用float類型參數。函數返回值類型在這個例子中可以不指定,因為sprintf函數返回值是string類型,F#可以推斷出f3的返回值是string類型。
?
?????let isEven =fun x -> x %2=0
?????F#支持lambda表達式,lambda表達式可以視為匿名函數。isEven接受一個lambda表達式,他的簽名為val isEven:int->bool,從簽名中可以看出他等同于let isEven x=x%2=0。
?
?????let fn f x=
?????match isEven x with
?????| true -> f(x)
?????| false ->()
?????函數式語言通常都有一種語法叫模式匹配(Pattern Match),他比c#中swith語句強大的多,不僅可以匹配基本原生類型,還支持通過自定義活動模式(Active Pattern,據我所知,這種語法只有F#提供了),來擴展。你可以理解為模式匹配為高級多態。
?????上句表示,如果某個整型數是偶數,那么叫執行一個函數f,否則返回()。函數式的模式匹配可以讓我們消除編寫if else,swith之類的條件判斷語句的需要。
函數fn的簽名為val fn:(int->unit)->int->unit
?
?????let print_even x= fn ( fun x -> printfn "%d is even" x) x
?????print_even 20
?????上面的代碼演示了如何調用函數print_even,如何把lambda表達式作為參數傳遞
?
?????2.如何定義遞歸函數
?????遞歸函數表示一個函數可以調用自身,一般的遞歸函數會大量的使用堆棧。但尾遞歸則不會,尾遞歸會使用迭代來代替遞歸。
?
Code?1let?breakn?n?(l:'a[])?=?
?2
?3????let?i?=ref?0?
?4
?5????let?rec?iter?()?=?
?6
?7????????if?!i>n?then?()?
?8
?9????????else?
10
11????????????printf?"%A"?l.[!i]?
12
13????????????i?:=!i+1?
14
15????????????iter?()?
16
17????iter?()?
18
19?
20
21breakn?8?[|0..10|]?
22
23
?
上面的代碼演示了有break語義函數。breakn是一個普通函數,它有兩個參數,一個是整型n,另一個是l,泛型數組。返回值是unit
let i =ref 0
i是一個int ref型的值,它引用了整數0,實際上ref是泛型函數,它返回一個Ref記錄類型,它的定義類似下面:
?
Code1?type?Ref<'a>={mutable?contents:'a}?
2?
3?let?ref?a={contents=a}?
4?
5?
?
breakn函數體內又定義了一個函數,而且這個函數使用了rec關鍵字,這說明兩點,在F#中函數可以內嵌,第二,遞歸函數要使用rec關鍵字來說明
ref類型的操作有一些不同,反引用ref類型值使用感嘆符!,而賦值使用:=。
上面的代碼編譯后,通過Reflector反編譯為c#代碼,可以看到變成了while循環。
?
下一篇:F# 學習之路(2) 深刻理解函數(下)
轉載于:https://www.cnblogs.com/lvxuwen/archive/2008/08/03/1256684.html
總結
以上是生活随笔為你收集整理的F#学习之路(2) 深刻理解函数(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 牛客假日团队赛5J护城河 bzoj 1
- 下一篇: 基于roslyn的动态编译库Natash
