Hydraxize
Hydraxize

Reputation: 171

While loop with immutable values in F#

I am using mutable variable in a F# while-do loop, it has been working well so far but I am curious to know if there is a way to make it purely functional and only use immutable data?

Context

The program listens to a Messenger and process its messages. The messages are sent in block and the aim of the program is to read them when they arrive and append all the results until the last message (MessageType.End) is received.
The code below is a simpler version of the real program but captures the main logic. (The only differences are in the treatment of MessageType.Status, MessageType.Failure and the parsing of the message body).

Program (F# Interactive version 12.0.0.0 for F# 6.0)

You should be able to run that code in the Interactive, if you have a lower version than 6.0, make the changes I indicated in the comments.

// ======================================================================================
// Type  
// ======================================================================================
type MessageType =
   | Partial
   | End
   | Status
   | Failure

type Message = {
   Type: MessageType
   Body: string
   ParsedResult: seq<int option>
}

// ======================================================================================
// Simulation of 3 messages (This is just to show an example of messages)
// ======================================================================================
let SimulatedMessages = [|
   {Type = MessageType.Status; Body = "My Status"; ParsedResult = [None] }
   {Type = MessageType.Partial; Body = "Immutability rocks..."; ParsedResult = [Some 1; Some 2; None] }
   {Type = MessageType.End; Body = "... when you know how to handle it"; ParsedResult = [Some 3] }
|]
let mutable SimulatedMessageIndex = 0

// if version < 6.0:
// replace _.NextMessage with this.NextMessage
// replace SimulatedMessages[SimulatedMessageIndex] with SimulatedMessages.[SimulatedMessageIndex]
type Messenger() =
   member _.NextMessage() =
      if SimulatedMessageIndex < 3 then
         let message = SimulatedMessages[SimulatedMessageIndex] 
         SimulatedMessageIndex <- SimulatedMessageIndex + 1
         message
      else failwith "SequenceIndex out of bound"


// ======================================================================================
// I added a field ParsedResult in the Message to simplify, in reality ParseMessage (message: Message)
// has a complex logic that parses the body to return a sequence of <a' option>
// ======================================================================================
let ParseMessage (message: Message) =
   message.ParsedResult

// ======================================================================================
// My infamous imperative algorithm
// ======================================================================================
let ListenMessenger() =
   let mutable result = Seq.empty<int option>
   let mutable isDone = false
   let messenger = Messenger()

   while not isDone do
      let message = messenger.NextMessage()
      match message.Type with 
      | MessageType.Status -> printfn "Log status"
      | MessageType.Partial -> result <- Seq.append result (ParseMessage message)
      | MessageType.End ->
         result <- Seq.append result (ParseMessage message)
         isDone <- true
      | MessageType.Failure -> 
         printfn "Alert failure"
         isDone <- true
   result

ListenMessenger() |> Seq.iter(printfn "%A")

Additional information

Each Message contains a MessageType and Body. In order to receive the next message I need to call NextMessage() until I receive the last one of type End. The Messenger always starts by sending a couple of MessageType.Status with no useful information in Body, then the messages are delivered in block MessageType.Partial until I receive the final block of type MessageType.End. I have added a field ParsedResult to keep the example simple, the true program has a complex parser that collects the information I need in the Body and return a sequence of optional type seq<a' option>.
Finally, I can't make any change on the Messenger or the Message structure, I'm the client who needs to adapt, the other side is the server who does not care about my immutability problems.

Challenge

Is it possible to modify that imperative code into purely immutable and functional code? There was some useful snippets in this post F# working with while loop and I know this can only be achieved by using yield! but I couldn't make my head around with the two MessageType that do not return anything MessageType.Status and MessageType.Failure. Apologize if this post is too long, I just wanted to give as many information as possible to scope the problem.

Upvotes: 2

Views: 251

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243051

First of all, you can replace the code that collects the results and returns them at the end with a sequence expression that returns all the results using yield! This still keeps the imperative loop, but it removes the imperative handling of results:

let ListenMessengerSeq() = seq {
  let mutable isDone = false
  let messenger = Messenger()
  while not isDone do
     let message = messenger.NextMessage()
     match message.Type with 
     | MessageType.Status -> printfn "Log status"
     | MessageType.Partial -> 
        yield! ParseMessage message
     | MessageType.End ->
        yield! ParseMessage message
        isDone <- true
     | MessageType.Failure -> 
        printfn "Alert failure"
        isDone <- true }

ListenMessengerSeq() |> Seq.iter(printfn "%A")

Now, you can remove the while loop by using a recursive sequence expression instead. To do this, you define a function loop that calls itself only in the cases when the computation is not done:

let ListenMessengerSeqRec() = 
  let messenger = Messenger()
  let rec loop () = seq {
     let message = messenger.NextMessage()
     match message.Type with 
     | MessageType.Status -> printfn "Log status"
     | MessageType.Partial -> 
        yield! ParseMessage message
        yield! loop ()
     | MessageType.End ->
        yield! ParseMessage message
     | MessageType.Failure -> 
        printfn "Alert failure" }
  loop()

ListenMessengerSeqRec() |> Seq.iter(printfn "%A")

There are many other ways of writing this, but I think this is quite clean - and you can also see that it is not too far from what you started with!

Upvotes: 4

Related Questions