F#入門:基本語法,模式匹配及List
F#隨著VSTS 2010 Beta1 發(fā)布也有一段時間了,園子里應該也有不少人對它感興趣吧。下面的例子是我在學F# 基本語法時寫的一個簡單Sieve of Eratosthenes 實現(xiàn),通過剖析這一小段代碼,我希望大家能對F#有個簡單認識,并能自己寫一些簡單的小程序。
F#入門代碼
- let GetAllPrimesBefore n =
- let container = Array.create (n+1) 0
- let rec loop acc = function
- |[] -> List.rev acc
- |hd::tl ->
- if container.[hd] =1 then
- loop acc tl
- else
- for j in [hd .. hd .. n] do
- container.[j] <- 1
- loop (hd::acc) tl
- loop [] [2 .. n]
- let primesBefore120 = GetAllPrimesBefore 120
廢話少說,直接進入正題吧
- let GetAllPrimesBefore n =
第一行,申明函數(shù)GetAllPrimesBefore, 并且該函數(shù)有一個參數(shù)n, 在這里我沒有指定n的類型,因為編繹器可以通過函數(shù)體對n的類型進去推斷,比如在本例中,n就是int類型,當然我們也可以顯示的指定n的類型,比如 let GetAllPrimesBefore (n:int),這樣我們就指定了n為int型 (注意:(n:int)中的括號不能省略,let GetAllPrimesBefore n : int 的意思是該函數(shù)返回的值的int型)。說完了參數(shù),再說下返回值,同樣,編繹器會根據(jù)函數(shù)體上下文對返回值類型進去推斷,所以我們不需要申明返回類型。
- let container = Array.create (n+1) 0
第二行,首先請注意該行與第一行相對有一個縮進({TAB}),F#和Python一樣,也是通過{TAB}縮進來組織代碼結構的。這一行我們定義了一個變量container,它的類型是Array,大小為 n+1, 并且值全部初使化為0
- let rec loop acc = function
- |[] -> List.rev acc
- |hd::tl ->
- if container.[hd] =1 then
- loop acc tl
- else
- for j in [hd .. hd .. n] do
- container.[j] <- 1
- loop (hd::acc) tl
接下來就是這個函數(shù)的主要部分了(原程序中的3-11行),首先我們定義了一個遞歸函數(shù)(我們發(fā)現(xiàn)定義遞歸函數(shù)需要加rec關鍵字)。它接受兩個參數(shù),acc和一個List,有朋友可能要問了,這里明明我只看到一個參數(shù)acc,你說的那個List在哪呢?可能有細心的朋友也發(fā)現(xiàn)了這里的函數(shù)定義不光前面有rec,在等號后面還加了個function,那么function是做什么用的呢?
- let rec loop acc = function
F#入門:模式匹配
這里我需要首先講一下Pattern Matching(模式匹配), Pattern Matching有些類似于C#中的switch語句(當然它要比C#中的switch強大許多,但這不是本文的目地,所以略去不表),可以根據(jù)expr的值去執(zhí)行某一具體分支,它的基本語法也很簡單,我們還是結合一個具體實例來看一下(例子比較簡單,只是為了說明問題)。 這個例子大家很容易看懂吧,我就不詳細解釋了,只是說明一點,'_'用來匹配所有別的情況。
- let ShowGreeting laguageInUse =
- match laguageInUse with
- | "C#" -> printfn "Hello, C# developer!"
- | "F#" -> printfn "Hello, F# developer!"
- |_ -> printfn "Hello, other developers!"
因為Pattern Matching在F#中的使用范圍實在太廣了,所以就引入了一種簡化版,這就是上面大家看到的等號后面的function的作用,我們可以把上面的例子簡化成
- let ShowGreeting = function
- | "C#" -> printfn "Hello, C# developer!"
- | "F#" -> printfn "Hello, F# developer!"
- |_ -> printfn "Hello, other developers!"
怎么樣?既少了給參數(shù)起名的煩惱,也少敲不少字吧,嘿嘿。
F#入門:List基本類型
接下來我再簡單介紹下F#中非常重要的一個基本類型List, 其基本表示形式為 [ item1;item2; .. ;itemn]
F#中List是immutable類型,我們只能訪問里面的值,不能改動里面的值,任何改動List的需求只能通過構建新的List來實現(xiàn)。稍一思考,大家就會很快發(fā)現(xiàn)要實現(xiàn)一個高效的immutable list, 那最簡單的就是對其頭結點進去操作了(插入和刪除都可以達到O(1),當然插入和刪除會構建一個新的List,原List不會改變),F(xiàn)#中的List也是基于這種形式,所有的List都可以看成是Head+Tail(除了Head外的所有結點),F#提供了相應的庫函數(shù)List.hd, List.tl,并且提供了:: (cons operator)來幫助我們方便的構建一個List,比如1::2::[]就表示List [1;2] (注意1和2之間我用的是;不是, 如果寫成[1,2],那個表示該List只有一個元素 (1,2),至于(1,2)是什么類型,為了使文章盡量緊湊,我們今天就不講了)
有了上面這些知識,再看本文一開始的函數(shù)就簡單多了
- let rec loop acc = function
- |[] -> List.rev acc
- |hd::tl ->
- if container.[hd] =1 then
- loop acc tl
- else
- for j in [hd .. hd .. n] do
- container.[j] <- 1
- loop (hd::acc) tl
首先,該函數(shù)的第二個參數(shù)是List,
當List為空時,就把acc反序返回,
當List不為空時,把List分成兩部分(hd::tl),檢查當當前值n (n的值等于td) 是否己被標記
如果己經(jīng)被標記(container.[hd] =1),略過當前值,檢查接下來的值 loop acc tl
如果沒有被標記(當前值是素數(shù)),用當前值和acc構建一個新List (hd::acc),并對當前值的所有倍數(shù)進去標記(for loop),然后檢查下一個值 loop (hd::acc) tl
這里有兩點需要特別說明一下:
1. container是一個Array類型的參數(shù),Array在F#中是mutable類型的容器,我們可以修改里面的元素,訪問元素用Array.[i], 修改元素用Array.<-[i] = newValue(不要忘記中間的.)
2. for loop的基本形式為 for <index> in <range> do, 我們可以使用[start .. end]或[start .. step .. end]來構建一個range,當然,這里的range其實也是一個List
看完了內部函數(shù),我們再接著往下看(原程序第12行)
- loop [] [2 .. n]
這里就很簡單了,調用我們剛剛定義的內部函數(shù),(acc為空List [], 第二個參數(shù)為List [2 .. n]),其返回值(List acc)就是函數(shù)GetAllPrimesBefore的返回值,F(xiàn)#中函數(shù)有返回值時不需要敲return.
函數(shù)調用也很簡單,(不需要在參數(shù)與函數(shù)名之間加括號)
- let primesBefore100 = GetAllPrimesBefore 100
后記
1. F#中函數(shù)體內可以定義新的值,變量和函數(shù)。(只在當前函數(shù)體內可見)。當然,這樣做的好處顯而易見,我就不啰嗦了。
2. Recursive function是functional programming中很常用的一種算法實現(xiàn)方式。functional programming language往往會針對尾遞歸進行特別的優(yōu)化,F(xiàn)#也不例外,所以我們需要盡可能的把遞歸寫成尾遞歸的形式,這個有時就需要像本文一樣借助accumulator來實現(xiàn)。
本文來自hiber的博客:《結合實例學習F#(一) --快速入門》。
【編輯推薦】