天天看點

F#入門:基本文法,模式比對及List

F#随着VSTS 2010 Beta1 釋出也有一段時間了,園子裡應該也有不少人對它感興趣吧。下面的例子是我在學F# 基本文法時寫的一個簡單Sieve of Eratosthenes 實作,通過剖析這一小段代碼,我希望大家能對F#有個簡單認識,并能自己寫一些簡單的小程式。

F#入門代碼

  1. let GetAllPrimesBefore n =   
  2.     let container = Array.create (n+1) 0  
  3.     let rec loop acc = function  
  4.         |[] -> List.rev acc  
  5.         |hd::tl ->   
  6.             if container.[hd] =1 then   
  7.                 loop acc tl  
  8.             else 
  9.                 for j in [hd .. hd .. n] do 
  10.                     container.[j] <- 1  
  11.                 loop (hd::acc) tl      
  12.     loop [] [2 .. n]  
  13. let primesBefore120 = GetAllPrimesBefore 120 

廢話少說,直接進入正題吧

  1. let GetAllPrimesBefore n = 

第一行,申明函數GetAllPrimesBefore, 并且該函數有一個參數n, 在這裡我沒有指定n的類型,因為編繹器可以通過函數體對n的類型進去推斷,比如在本例中,n就是int類型,當然我們也可以顯示的指定n的類型,比如 let GetAllPrimesBefore (n:int),這樣我們就指定了n為int型 (注意:(n:int)中的括号不能省略,let GetAllPrimesBefore n : int 的意思是該函數傳回的值的int型)。說完了參數,再說下傳回值,同樣,編繹器會根據函數體上下文對傳回值類型進去推斷,是以我們不需要申明傳回類型。

  1. let container = Array.create (n+1) 0 

第二行,首先請注意該行與第一行相對有一個縮進({TAB}),F#和Python一樣,也是通過{TAB}縮進來組織代碼結構的。這一行我們定義了一個變量container,它的類型是Array,大小為 n+1, 并且值全部初使化為0

  1. let rec loop acc = function  
  2.         |[] -> List.rev acc  
  3.         |hd::tl ->   
  4.             if container.[hd] =1 then   
  5.                 loop acc tl  
  6.             else 
  7.                 for j in [hd .. hd .. n] do 
  8.                     container.[j] <- 1  
  9.                 loop (hd::acc) tl  

接下來就是這個函數的主要部分了(原程式中的3-11行),首先我們定義了一個遞歸函數(我們發現定義遞歸函數需要加rec關鍵字)。它接受兩個參數,acc和一個List,有朋友可能要問了,這裡明明我隻看到一個參數acc,你說的那個List在哪呢?可能有細心的朋友也發現了這裡的函數定義不光前面有rec,在等号後面還加了個function,那麼function是做什麼用的呢?

  1. let rec loop acc = function 

F#入門:模式比對

這裡我需要首先講一下Pattern Matching(模式比對), Pattern Matching有些類似于C#中的switch語句(當然它要比C#中的switch強大許多,但這不是本文的目地,是以略去不表),可以根據expr的值去執行某一具體分支,它的基本文法也很簡單,我們還是結合一個具體執行個體來看一下(例子比較簡單,隻是為了說明問題)。 這個例子大家很容易看懂吧,我就不詳細解釋了,隻是說明一點,'_'用來比對所有别的情況。

  1. let ShowGreeting laguageInUse =   
  2.     match laguageInUse with  
  3.     | "C#" -> printfn "Hello, C# developer!" 
  4.     | "F#" -> printfn "Hello, F# developer!" 
  5.     |_ -> printfn "Hello, other developers!" 

因為Pattern Matching在F#中的使用範圍實在太廣了,是以就引入了一種簡化版,這就是上面大家看到的等号後面的function的作用,我們可以把上面的例子簡化成

  1. let ShowGreeting  = function      
  2.     | "C#" -> printfn "Hello, C# developer!" 
  3.     | "F#" -> printfn "Hello, F# developer!" 
  4.     |_ -> 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)是什麼類型,為了使文章盡量緊湊,我們今天就不講了)

有了上面這些知識,再看本文一開始的函數就簡單多了

  1. let rec loop acc = function  
  2.        |[] -> List.rev acc  
  3.        |hd::tl ->   
  4.            if container.[hd] =1 then   
  5.                loop acc tl  
  6.            else 
  7.                for j in [hd .. hd .. n] do 
  8.                    container.[j] <- 1  
  9.                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行)

  1. loop [] [2 .. n] 

這裡就很簡單了,調用我們剛剛定義的内部函數,(acc為空List [], 第二個參數為List [2 .. n]),其傳回值(List acc)就是函數GetAllPrimesBefore的傳回值,F#中函數有傳回值時不需要敲return.

函數調用也很簡單,(不需要在參數與函數名之間加括号)

  1. let primesBefore100 = GetAllPrimesBefore 100 

後記

1. F#中函數體内可以定義新的值,變量和函數。(隻在目前函數體内可見)。當然,這樣做的好處顯而易見,我就不啰嗦了。

2. Recursive function是functional programming中很常用的一種算法實作方式。functional programming language往往會針對尾遞歸進行特别的優化,F#也不例外,是以我們需要盡可能的把遞歸寫成尾遞歸的形式,這個有時就需要像本文一樣借助accumulator來實作。

本文來自hiber的部落格:《結合執行個體學習F#(一) --快速入門》。

【編輯推薦】

  1. C# Actor的尴尬與F#美麗外表下的遺憾
  2. 函數式程式設計語言F#:基于CLR的另一個頭等程式設計語言
  3. Visual Studio 2010爆F#二進制相容性問題
  4. 推薦Visual Studio 2010中F#的一些資源
  5. Visual Studio 2010将正式包含F#

繼續閱讀