简体   繁体   English

F# 中具有不可变值的 While 循环

[英]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循环中使用可变变量,到目前为止它一直运行良好,但我很想知道是否有办法让它成为纯粹的功能并且只使用不可变数据?

Context语境

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.StatusMessageType.Failure的处理和消息正文的解析)。

Program (F# Interactive version 12.0.0.0 for F# 6.0)程序(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.您应该能够在 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")

Additional information附加信息

Each Message contains a MessageType and Body .每个Message包含一个MessageTypeBody 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.StatusBody中没有有用的信息,然后消息在块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 结构进行任何更改,我是需要适配的客户端,另一端是不关心我的不可变性问题的服务器。

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!这篇文章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.StatusMessageType.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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM