[英]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.iter
比for loop
更糟糕for loop
因為第一個創建一個鏈表(F#不可變列表是引擎蓋下的單個鏈表)並迭代它。 在許多情況下,增加的CPU和內存使用量是不相關的,但在其他情況下則是如此。
像Seq
這樣的懶惰集合可以緩解這個問題,但是不幸的Seq
目前在F#中效率不高。 Nessos Streams將是更好的選擇。
F#中for loop
的問題在於它不能過早地break
(F#中不存在break
或continue
)。
此外, 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.