Search code examples
f#immutabilitypurely-functional

F# Immutability, pure function and side effect


I'm new to F# and I'm coding little challenges to learn the nitty-gritty details about the language. I think I have a problem because of immutability.

Scenario: I have to read height lines in the console, each line contains one integer. That integer represent the size of a mountain. After reading the input i need to write the line number of the highest mountains. If the index given is the highest mountain then the size is set to zero else I loose. Repeat the scenario until all mountains have their size set to zero.

Here the code I wrote:

open System

type Mountain = {Id:int; Height:int}

let readlineInt() = int(Console.In.ReadLine())
let readMountainData id = {Id = id; Height = readlineInt()}
let readAllMountainsData = [ for a in 0 .. 7 do yield readMountainData a ]

let rec mainLoop () =
     let mountains = readAllMountainsData
     let highestMountain = mountains |> List.maxBy (fun x -> x.Height)

     printfn "%i" highestMountain.Id
     mainLoop()

mainLoop()

This code is going to an infinite loop, I believe it's because the

let readlineInt() = int(Console.In.ReadLine())

is immutable, so the value is set once and after it's never stop again to read the line. I try to put 'mutable' keyword for

let mutable readAllMountainsData = [ for a in 0 .. 7 do yield readMountainData a ]

But it didn't change a thing. Do you have any idea?

Edit: I know that this code is going into an infinite loop because after adding logging into the main loop as follow:

let rec mainLoop () =
     let mountains = readAllMountainsData
     Console.Error.WriteLine("Mountain Count:{0} ", mountains.Length)
     mountains |> List.iter (fun x -> Console.Error.WriteLine("Mountain Id:{0} Height:{1}", x.Id, x.Height))
     let highestMountain = mountains |> List.maxBy (fun x -> x.Height)

     printfn "%i" highestMountain.Id
     mainLoop()

Then I have this in the output:

Standard Error Stream:

Mountain Count:8 
Mountain Id:0 Height:9
Mountain Id:1 Height:8
Mountain Id:2 Height:7
Mountain Id:3 Height:6
Mountain Id:4 Height:5
Mountain Id:5 Height:4
Mountain Id:6 Height:3
Mountain Id:7 Height:2
Mountain Count:8 
Mountain Id:0 Height:9
Mountain Id:1 Height:8
Mountain Id:2 Height:7
Mountain Id:3 Height:6
Mountain Id:4 Height:5
Mountain Id:5 Height:4
Mountain Id:6 Height:3
Mountain Id:7 Height:2
Mountain Count:8 
Mountain Id:0 Height:9
Mountain Id:1 Height:8
Mountain Id:2 Height:7
etc...

Why do I want to reread the value? Because the values are provided by an external source. So the workflow is as follow:

Loop one:
I read 8 values for the height of the mountains in the console
I output the value of the highest mountain

Loop two:
I read 8 values for the height of the mountains in the console
I output the value of the highest mountain

Loop three:
I read 8 values for the height of the mountains in the console
I output the value of the highest mountain

etc

Solution

  • let readlineInt () = ... defines a function. It's body will be executed every time you call it. And in this case the body has a side-effect and that side-effect (reading from stdin) will be executed every time the body is executed. So that's not your problem.

    readAllMountainsData is defined to be a list containing the data of seven mountains. Each of those mountains will have its own height (because readLineInt() is called once for each mountain). This list is calculated once and does not change after that. It is not re-calculated every time you use readAllMountainsData as it is a variable, not a function (even though the name might suggest otherwise). That seems perfectly sensible as re-reading the mountain data every time would make no sense.

    Adding the mutable keyword to the definition allows you to re-assign the variable. That is, it allows you to write readAllMountainsData <- someNewValue later in the program to change the variable's value. Since you never actually do that, nothing changes.

    The reason that your program loops infinitely is that mainLoop always calls itself again. It has no exit condition. So to fix that you should decide how often you want to loop / under which condition you want to exit, and then implement that logic accordingly.


    In your edit you clarified, that you do want to re-read your values, so you simply need to make readAllMountainsData a function by giving it a parameter list (let readAllMountainsData () = ...) and then call it as a function. This way you'll get new data on each iteration, but the loop will still be infinite unless you add an exit condition.