結合例項學習F#(一) --快速入門

iDotNetSpace發表於2009-08-12
 F#隨著VSTS 2010 Beta1 釋出也有一段時間了,園子裡應該也有不少人對它感興趣吧。下面的例子是我在學F# 基本語法時寫的一個簡單Sieve of Eratosthenes 實現,通過剖析這一小段程式碼,我希望大家能對F#有個簡單認識,並能自己寫一些簡單的小程式。
 1 let GetAllPrimesBefore n = 
 2     let container = Array.create (n+10
 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     
14 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型)。說完了引數,再說下返回值,同樣,編繹器會根據函式體上下文對返回值型別進去推斷,所以我們不需要申明返回型別。

2    let container = Array.create (n+10

第二行,首先請注意該行與第一行相對有一個縮排({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是做什麼用的呢?

let rec loop acc = function

  這裡我需要首先講一下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, 其基本表示形式為 [ 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.      2.  for loop的基本形式為 for in 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來實現。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-612011/,如需轉載,請註明出處,否則將追究法律責任。

相關文章