F#随着VSTS 2010 Beta1 釋出也有一段時間了,園子裡應該也有不少人對它感興趣吧。下面的例子是我在學F# 基本文法時寫的一個簡單Sieve of Eratosthenes 實作,通過剖析這一小段代碼,我希望大家能對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 =
第一行,申明函數GetAllPrimesBefore, 并且該函數有一個參數n, 在這裡我沒有指定n的類型,因為編繹器可以通過函數體對n的類型進去推斷,比如在本例中,n就是int類型,當然我們也可以顯示的指定n的類型,比如 let GetAllPrimesBefore (n:int),這樣我們就指定了n為int型 (注意:(n:int)中的括号不能省略,let GetAllPrimesBefore n : int 的意思是該函數傳回的值的int型)。說完了參數,再說下傳回值,同樣,編繹器會根據函數體上下文對傳回值類型進去推斷,是以我們不需要申明傳回類型。
- 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
接下來就是這個函數的主要部分了(原程式中的3-11行),首先我們定義了一個遞歸函數(我們發現定義遞歸函數需要加rec關鍵字)。它接受兩個參數,acc和一個List,有朋友可能要問了,這裡明明我隻看到一個參數acc,你說的那個List在哪呢?可能有細心的朋友也發現了這裡的函數定義不光前面有rec,在等号後面還加了個function,那麼function是做什麼用的呢?
- let rec loop acc = function
F#入門:模式比對
這裡我需要首先講一下Pattern Matching(模式比對), Pattern Matching有些類似于C#中的switch語句(當然它要比C#中的switch強大許多,但這不是本文的目地,是以略去不表),可以根據expr的值去執行某一具體分支,它的基本文法也很簡單,我們還是結合一個具體執行個體來看一下(例子比較簡單,隻是為了說明問題)。 這個例子大家很容易看懂吧,我就不詳細解釋了,隻是說明一點,'_'用來比對所有别的情況。
- 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!"
怎麼樣?既少了給參數起名的煩惱,也少敲不少字吧,嘿嘿。
F#入門:List基本類型
接下來我再簡單介紹下F#中非常重要的一個基本類型List, 其基本表示形式為 [ item1;item2; .. ;itemn]
F#中List是immutable類型,我們隻能通路裡面的值,不能改動裡面的值,任何改動List的需求隻能通過建構新的List來實作。稍一思考,大家就會很快發現要實作一個高效的immutable list, 那最簡單的就是對其頭結點進去操作了(插入和删除都可以達到O(1),當然插入和删除會建構一個新的List,原List不會改變),F#中的List也是基于這種形式,所有的List都可以看成是Head+Tail(除了Head外的所有結點),F#提供了相應的庫函數List.hd, List.tl,并且提供了:: (cons operator)來幫助我們友善的建構一個List,比如1::2::[]就表示List [1;2] (注意1和2之間我用的是;不是, 如果寫成[1,2],那個表示該List隻有一個元素 (1,2),至于(1,2)是什麼類型,為了使文章盡量緊湊,我們今天就不講了)
有了上面這些知識,再看本文一開始的函數就簡單多了
- 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
首先,該函數的第二個參數是List,
當List為空時,就把acc反序傳回,
當List不為空時,把List分成兩部分(hd::tl),檢查當目前值n (n的值等于td) 是否己被标記
如果己經被标記(container.[hd] =1),略過目前值,檢查接下來的值 loop acc tl
如果沒有被标記(目前值是素數),用目前值和acc建構一個新List (hd::acc),并對目前值的所有倍數進去标記(for loop),然後檢查下一個值 loop (hd::acc) tl
這裡有兩點需要特别說明一下:
1. container是一個Array類型的參數,Array在F#中是mutable類型的容器,我們可以修改裡面的元素,通路元素用Array.[i], 修改元素用Array.<-[i] = newValue(不要忘記中間的.)
2. for loop的基本形式為 for <index> in <range> do, 我們可以使用[start .. end]或[start .. step .. end]來建構一個range,當然,這裡的range其實也是一個List
看完了内部函數,我們再接着往下看(原程式第12行)
- loop [] [2 .. n]
這裡就很簡單了,調用我們剛剛定義的内部函數,(acc為空List [], 第二個參數為List [2 .. n]),其傳回值(List acc)就是函數GetAllPrimesBefore的傳回值,F#中函數有傳回值時不需要敲return.
函數調用也很簡單,(不需要在參數與函數名之間加括号)
- let primesBefore100 = GetAllPrimesBefore 100
後記
1. F#中函數體内可以定義新的值,變量和函數。(隻在目前函數體内可見)。當然,這樣做的好處顯而易見,我就不啰嗦了。
2. Recursive function是functional programming中很常用的一種算法實作方式。functional programming language往往會針對尾遞歸進行特别的優化,F#也不例外,是以我們需要盡可能的把遞歸寫成尾遞歸的形式,這個有時就需要像本文一樣借助accumulator來實作。
本文來自hiber的部落格:《結合執行個體學習F#(一) --快速入門》。
【編輯推薦】
- C# Actor的尴尬與F#美麗外表下的遺憾
- 函數式程式設計語言F#:基于CLR的另一個頭等程式設計語言
- Visual Studio 2010爆F#二進制相容性問題
- 推薦Visual Studio 2010中F#的一些資源
- Visual Studio 2010将正式包含F#