2017-06-04 85 views
3

我有這個簡單的「投資」型與「壽命」屬性:F# - 把簡單的for循環到更多的功能結構

type Investment = { 
PurchasePayment: float; 
Lifetime: float; 
TaxWriteoff: float; 
ResidualValue: float; 
} 

let CoffeMachine = { 
    PurchasePayment = 13000.0; 
    Lifetime = 4.0; 
    TaxWriteoff = 2000.0; 
    ResidualValue = 500.0; 
} 

我想遍歷多年的投資和執行一些計算的每年:

for i = 1 to int CoffeMachine.Lifetime do 
    printf "%i" i 
    // doSomething 

有沒有辦法避免使用for循環,並寫在一個更實用的風格?

乾杯,

WOJTEK

+0

這取決於如何壽命將在生產代碼產生,但使用' int'函數可能不是一個好主意。從DB或其他意想不到的計算或檢索可能會導致3.9 ...而不是4.0,然後使用'int'後,你將有3年而不是4年。也許你應該用'round'來代替。 –

回答

5

去到在列表中的每個項目執行計算的方法被稱爲map。具體而言,對於你的情況,你可以從1號創建的列表Lifetime然後用List.map執行每個計算:

let calculation year = year * 2 // substitute your calculation here 

let calcResults = [1..int CoffeMachine.Lifetime] |> List.map calculation 

這麼說,我想你混淆「功能」與「硬理解「(或者,也許是」炫耀「)。 「功能性程序設計」的重點不在於數學,而且對於初學者來說是不可接近的。 「功能編程」的要點是「用功能編程」。這有一些實際意義,比如「不變數據」和「無副作用」,只要你的程序滿足這些要求,你可以認爲它是「功能性」的,無論它看起來很簡單。事實上,我認爲它看起來越簡單越好。軟件可維護性是一個非常有價值的目標。

特別是,如果您只是想打印年份,那麼您的代碼就很好:打印出來本身就是一種「副作用」,所以只要這是一項要求,就沒有辦法讓它更「功能」。但是,如果你的目標是執行某些計算(如上面的示例所示),那麼可以表達與列表理解清潔:

let calcResults = [for year in 1..int CoffeMachine.Lifetime -> calculation year] 
2

我很快找到了答案:

[1..int CoffeMachine.Lifetime] |> List.iter (printf "%i") 
1

從其他答案一種不同的方法是使用尾遞歸。

例如,尾遞歸的好處是什麼?

[1..int CoffeMachine.Lifetime] |> List.iter (printf "%i") 

或者:

for i = 1 to int CoffeMachine.Lifetime do 
    printf "%i" i 

從性能和內存的角度List.iter比差了for loop,因爲第一個創建一個單鏈表(F#不可改變的名單是引擎蓋下單鏈表)和迭代超過它。在許多情況下,增加的CPU和內存使用量並不相關,但在其他情況下是這樣。

Seq這樣的懶惰集合可以緩解這種情況,但不幸的是Seq目前在F#中效率不高。 Nessos Streams將是更好的選擇。

F#中for loop的問題在於它不能過早地被破壞(breakcontinue在F#中不存在)。

此外,當彙總結果時,for loop模式通常會迫使我們進入可變變量模式。

尾遞歸允許我們聚合結果而不依賴於可變變量並支持中止。此外,尾遞歸循環可以返回值for loop不能(表達式結果總是unit

F#中的尾遞歸也是有效的,因爲F#檢測尾遞歸函數並將其展開到引擎蓋下的循環中。

這是上面的代碼如何尾遞歸循環可能看起來像:

let rec loop i l = if i <= l then printf "%i" i; loop (i + 1) l 
loop 1 (int CoffeMachine.Lifetime) 

使用ILSpy一個看到這個被編譯成一個while循環:

internal static void [email protected](int i, int l) 
{ 
    while (i <= l) 
    { 
    PrintfFormat<FSharpFunc<int, Unit>, TextWriter, Unit, Unit> format = new PrintfFormat<FSharpFunc<int, Unit>, TextWriter, Unit, Unit, int>("%i"); 
    PrintfModule.PrintFormatToTextWriter<FSharpFunc<int, Unit>>(Console.Out, format).Invoke(i); 
    int arg_28_0 = i + 1; 
    l = l; 
    i = arg_28_0; 
    } 
} 
0

一對夫婦的簡短示例遞歸的方式來做到這一點。通常情況下,您只需使用.iter或.map。

let rec map f = function 
    | [] -> [] 
    | h::t -> f h::map f t 

map (fun x->x+1) [1;2;3] 

let rec iter f = function 
    | [] ->() 
    | h::t -> 
     f h 
     iter f t 

簽名:

for iter, f:('a -> unit) 

for map, f:('a -> 'b) 

你得到標準循環語法,太:

for i in [0 .. 4] do 
    printfn "%i" i 

for i = 0 to 4 do 
    printfn "%i" i 

要在功能性的方式使用for循環,這句法可以方便:

[for i in [0..10]->i] 

[for i = 0 to 10 do yield i] 

請注意,

List.map F [0..10]等於[對於i在[0..10] - > I]