F # - Turn a simple for loop into a more functional construct

I have this simple "Investment" type with the "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 repeat over the years of investment and do some calculations for each year:

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

      

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

Greetings,

Wojtek

+3


source to share


4 answers


The step-by-step method of performing calculations for each item in the 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 of them:

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

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

      

However, I think you are misleading "functional" with "hard to understand" (or perhaps "brag"). The point of "functional programming" does not have to be mathematics and is inaccessible to the uninitiated. The point of "functional programming" is "programming with functions". There are some practical implications of this, such as "immutable data" and "no side effects," and as long as your program satisfies them, you can think of it as "functional", no matter how simple it looks. In fact, I would say that the simpler it looks, the better. Software support is a very worthy goal.



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

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

      

+5


source


I quickly found the answer:



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

      

+2


source


Another approach from other answers is to use tail recursion.

What are the benefits of tail recursion, for example?

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

      

Or:

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

      

In terms of performance and memory, List.iter

worse than for loop

that because one linked list is created first (F # immutable lists is a single list under the hood) and iterated over it. In many situations, the increase in CPU and memory usage doesn't matter, but in other situations it does.

Lazy collections such as Seq

would lessen this, but the unfortunate one Seq

is currently not efficient in F #. Nessos Streams will be the best choice.

The problem with for loop

in F # is that it cannot be broken prematurely ( break

or continue

does not exist in F #).

In addition, the pattern for loop

often leads us to a mutable variable pattern when aggregating the results.

Tail recursion allows us to generalize results without relying on mutable variables and supporting interruption. Also, tail-recursive loops can return values ​​that for loop

cannot (expression result always unit

)

Tail recursion is also efficient in F # because F # detects tail recursive functions and loops that under the hood.

This is what the tail-recursive loop of the code above would 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

, you can see that 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;
  }
}

      

+1


source


A few short examples of recursive ways to do this. Usually, you just use .iter or .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

      

Signatures:

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

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

      

You also get the standard loop syntax:

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 handy:

[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 from [0..10] β†’ i]

0


source







All Articles