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
source to share
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]
source to share
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;
}
}
source to share
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]
source to share