Reputation: 171
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?
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).
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")
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.
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
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