schmoopy
schmoopy

Reputation: 6649

Avoid mutation in this example in F#

Coming from an OO background, I am having trouble wrapping my head around how to solve simple issues with FP when trying to avoid mutation.

let mutable run = true
let player1List = ["he"; "ho"; "ha"]

let addValue lst value =
    value :: lst

while run do
    let input = Console.ReadLine()
    addValue player1List input |> printfn "%A"
    if player1List.Length > 5 then 
        run <- false
        printfn "all done" // daz never gunna happen

I know it is ok to use mutation in certain cases, but I am trying to train myself to avoid mutation as the default. With that said, can someone please show me an example of the above w/o using mutation in F#?

The final result should be that player1List continues to grow until the length of items are 6, then exit and print 'all done'

Upvotes: 3

Views: 347

Answers (4)

kimsk
kimsk

Reputation: 2231

In F#, we use recursion to do loop. However, if you know how many times you need to iterate, you could use F# List.fold like this to hide the recursion implementation.

[1..6] |> List.fold (fun acc _ -> Console.ReadLine()::acc) []

Upvotes: 2

Tomas Petricek
Tomas Petricek

Reputation: 243051

As others already explained, you can rewrite imperative loops using recursion. This is useful because it is an approach that always works and is quite fundamental to functional programming.

Alternatively, F# provides a rich set of library functions for working with collections, which can actually nicely express the logic that you need. So, you could write something like:

let player1List = ["he"; "ho"; "ha"]
let player2List = Seq.initInfinite (fun _ -> Console.ReadLine())
let listOf6 = Seq.append player1List list2 |> Seq.take 6 |> List.ofSeq 

The idea here is that you create an infinite lazy sequence that reads inputs from the console, append it at the end of your initial player1List and then take first 6 elements.

Depending on what your actual logic is, you might do this a bit differently, but the nice thing is that this is probably closer to the logic that you want to implement...

Upvotes: 4

Petr
Petr

Reputation: 4280

I would remove the pipe from match for readability but use it in the last expression to avoid extra brackets:

open System

let rec makelist l = 
    match List.length l with
    | 6 -> printfn "all done"; l
    | _ -> Console.ReadLine()::l |> makelist

makelist []

Upvotes: 1

John Palmer
John Palmer

Reputation: 25516

The easiest way is to use recursion

open System
let rec makelist l = 
    match l |> List.length with
    |6  -> printfn "all done"; l
    | _ -> makelist ((Console.ReadLine())::l)

makelist []

I also removed some the addValue function as it is far more idiomatic to just use :: in typical F# code.

Your original code also has a common problem for new F# coders that you use run = false when you wanted run <- false. In F#, = is always for comparison. The compiler does actually warn about this.

Upvotes: 7

Related Questions