Reputation: 6446
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
Upvotes: 6
Views: 1829
Reputation: 9019
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]
Upvotes: 0
Reputation: 11577
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;
}
}
Upvotes: 2
Reputation: 80744
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]
Upvotes: 9
Reputation: 6446
I quickly found an answer:
[1..int CoffeMachine.Lifetime] |> List.iter (printf "%i")
Upvotes: 2