[英]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?我在 F#
while-do
循环中使用可变变量,到目前为止它一直运行良好,但我很想知道是否有办法让它成为纯粹的功能并且只使用不可变数据?
The program listens to a Messenger and process its messages.该程序侦听 Messenger 并处理其消息。 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.消息按块发送,程序的目的是在消息到达时读取它们,并读取所有结果 append 直到收到最后一条消息 (
MessageType.End
)。
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). (唯一的区别在于
MessageType.Status
、 MessageType.Failure
的处理和消息正文的解析)。
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.您应该能够在 Interactive 中运行该代码,如果您的版本低于 6.0,请进行我在评论中指出的更改。
// ======================================================================================
// 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
.每个
Message
包含一个MessageType
和Body
。 In order to receive the next message I need to call NextMessage()
until I receive the last one of type End
.为了接收下一条消息,我需要调用
NextMessage()
直到收到最后一条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
. Messenger 总是首先发送几个
MessageType.Status
, Body
中没有有用的信息,然后消息在块MessageType.Partial
中传递,直到我收到类型为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>
.我添加了一个字段
ParsedResult
以保持示例简单,真正的程序有一个复杂的解析器,它在Body
中收集我需要的信息并返回可选类型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.最后,我无法对 Messenger 或 Message 结构进行任何更改,我是需要适配的客户端,另一端是不关心我的不可变性问题的服务器。
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!
这篇文章F# 中有一些有用的片段使用 while 循环,我知道这只能通过使用
yield!
but I couldn't make my head around with the two MessageType
that do not return anything MessageType.Status
and MessageType.Failure
.但是我无法理解不返回任何
MessageType.Status
和MessageType.Failure
MessageType
Apologize if this post is too long, I just wanted to give as many information as possible to scope the problem.如果这篇文章太长,我深表歉意,我只是想尽可能多地向 scope 提供有关问题的信息。
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!
首先,您可以将收集结果并在最后返回它们的代码替换为使用
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.现在,您可以改用递归序列表达式来移除
while
循环。 To do this, you define a function loop
that calls itself only in the cases when the computation is not done:为此,您定义了一个 function
loop
,该循环仅在计算未完成的情况下调用自身:
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!还有很多其他的写法,但我认为这很干净——而且你也可以看到它与你开始的地方相差不远!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.