简体   繁体   中英

F# - Turn simple for loop into more functional construct

I have this simple "Investment" type with a "lifetime" property:

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

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

I would like to iterate over the years of investment and perform some calculations for each year:

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

Is there a way to avoid using for loop for that and write it in a more functional style?

Cheers,

Wojtek

The go-to method of performing calculations on every item in a list is called map . Specifically, for your case, you can create a list of numbers from 1 to Lifetime and then use List.map to perform a calculation on each:

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

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

That said, I think you're confusing "functional" with "hard to understand" (or, perhaps, "show off"). The point of "functional programming" is not to be all mathematics and inaccessible to the uninitiate. The point of "functional programming" is "programming with functions". There are some practical implications of that, such as "immutable data" and "no side effects", and as long as your program satisfies those, you can consider it "functional", however simple it may look. In fact, I would argue that the simpler it looks, the better. Software maintainability is a very worthy goal.

In particular, if you just wanted to print out the years, then your code is fine: printing out itself is a "side effect", so as long as that's a requirement, there is no way to make it more "functional". But if your goal is to perform some calculation (as in my example above), then that could be expressed cleaner with a list comprehension:

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

我很快找到了答案:

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

A different approach from other answers would be to use tail recursion.

What are the benefits of tail recursion over for example?

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

Or:

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

From a performance and memory perspective List.iter is worse than the for loop because first one create a single linked list (F# immutable lists are single linked list under the hood) and iterate over it. In many situations the increased CPU and memory usage is not relevant but in other situations it is.

Lazy collections like Seq would mitigate this but unfortunate Seq is currently not efficient in F#. Nessos Streams would be a better choice.

The problem with the for loop in F# is that it can't be broken out of prematurely ( break or continue doesn't exist in F#).

In addition, the for loop pattern often forces us into a mutable variable pattern when aggregating results.

Tail recursion allows us to aggregate results without relying on mutable variables and supports aborting. In addition, tail recursion loops can return values which for loop can't (the expression result is always unit )

Tail recursion is also efficient in F# as F# detects tail recursive functions and unrolls this to a loop under the hood.

This is how a tail recursive loop the code above could look like:

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

Using ILSpy one sees this is compiled into a while loop:

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;
  }
}

A couple of short examples of recursive ways to do this. Usually, you'll just use .iter or .map, though.

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

Signatures:

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

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

You get standard looping syntax, too:

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

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

To use a for loop in a functional way, this syntax can be convenient:

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

[for i = 0 to 10 do yield i]

note that

List.map f [0..10] is equivalent to [for i in [0..10]->i]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM