簡體   English   中英

F# - 將簡單的循環轉換為更多功能構造

[英]F# - Turn simple for loop into more functional construct

我有這種簡單的“投資”類型,具有“終身”屬性:

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

對列表中的每個項目執行計算的首選方法稱為map 具體來說,對於您的情況,您可以創建從1到Lifetime的數字列表,然后使用List.map對每個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]

我很快找到了答案:

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

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

例如,尾遞歸有什么好處?

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

要么:

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

從性能和內存的角度來看, List.iterfor loop更糟糕for loop因為第一個創建一個鏈表(F#不可變列表是引擎蓋下的單個鏈表)並迭代它。 在許多情況下,增加的CPU和內存使用量是不相關的,但在其他情況下則是如此。

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

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

此外, 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 loop@3(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;
  }
}

這幾個遞歸方法的簡短示例。 通常,您只需使用.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]相當於[for i in [0..10] - > i]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM