【问题标题】:F# Pattern-matching & recursion vs looping & if..then's for parsing nested structuresF# Pattern-matching & recursion vs looping and if..then 用于解析嵌套结构
【发布时间】:2013-08-02 05:57:53
【问题描述】:

我在 F# 中使用第三方供应商的 API。初始化时,API 返回一个嵌套 msg 容器的 C# 对象。它填充了状态消息,并且可能包含错误消息。供应商提供了我已移植 F# 的 C# 示例解析例程。

代码示例循环通过嵌套的 msg 容器提取致命和非致命错误,然后返回 BBResponseType * string 类型的元组列表

响应枚举:

type BBResponseType =
    | Status = 0
    | Data = 1
    | Error = 2
    | FatalError = -1 

我的 F# 端口如下所示:

 member private this.ProcessStatusMsg(eventObj: Blpapi.Event) = 
     let responseLst = List<(BBResponseType * string)>()
     for msg in eventObj do
         if msg.MessageType.Equals(SUBSTARTED) then
             if msg.GetElement(EXCEPTIONS).NumValues > 0 then // <- check for errors/exceptions                        
                 let e = msg.GetElement(EXCEPTIONS)
                 for i in 0..e.NumValues-1 do                                
                     let error = e.GetValueAsElement(i)
                     let field = error.GetElementAsString(FieldID)
                     let reason = error.GetElement(REASON)
                     let message = sprintf "Subscription Started w/errors( Field:   %s \n   Reason:   %s)" field (reason.GetElementAsString(DESCRIPTION))                                
                     responseLst.Add(BBResponseType.Error, message)
             else                                
                 let message = sprintf "Subscription Started"         
                 responseLst.Add(BBResponseType.Status, message)

         if msg.MessageType.Equals(SUBSCFAILURE) then // <- check for subscriptions failure
             if msg.HasElement(REASON) then
                 let reason = msg.GetElement(REASON)
                 let desc = reason.GetElementAsString(DESCRIPTION)
                 let message = sprintf "Real-time Subscription Failure:    %s" desc                            
                 responseLst.Add(BBResponseType.FatalError, message)
             else
                 let message = sprintf "Subscription Failure:  (reason unknown) "                                                
                 responseLst.Add(BBResponseType.FatalError, message)
     responseLst

完成后,我看着它并想,“哇,这几乎是你所能得到的没有功能的东西,而且仍然在 F# 中编码。”

它看起来确实比 C# 版本更清晰和简洁,但我认为必须有更好的方法来完成这一切,而无需使用这么多循环和 if/then's。

如何使用模式匹配和递归更好地解析这些嵌套结构?

【问题讨论】:

  • 仅供参考,您必须手动分配赏金。

标签: recursion f# pattern-matching nested-loops c#-to-f#


【解决方案1】:

几个指针:

  1. 不是返回一个元组列表而是返回一个元组序列 - 使用 seq { } 计算表达式来创建序列。
  2. 将 if/else 部分提取为 Message -&gt; (BBResponseType * string) 类型的函数,并在 seq 表达式中使用此函数
  3. 在这个新函数(将消息转换为元组)中,使用模式匹配来确定要返回的 (BBResponseType * string) 类型。

【讨论】:

  • 我喜欢这个答案,并投了赞成票,但作为一个 F# 初学者,一点代码会很有帮助
  • @ChrisTarn:我同意,很遗憾我没有 F# 环境,并且 tryfsharp.org 不能在 Linux 上运行 :(
【解决方案2】:

补充@Ankur 的回答:

member private this.ProcessStatusMsg(eventObj: Blpapi.Event) =
    // 0. Define a parameterized active pattern to turn if/else into pattern matching
    let (|Element|_|) e msg =
        if msg.HasElement(e) then
            Some <| msg.GetElement(e)
        else None

    // 1. Wrapping the whole method body in a sequence expression 
    seq {
        for msg in eventObj do
            // 2. Extracting if/else part and using it in sequence expression
            match msg.MessageType with
            // 3. Using pattern matching to figure out what kind (BBResponseType * string) to return
            | SUBSTARTED ->
                match msg with
                // 4. Use active pattern to pattern match on message directly
                | Element EXCEPTIONS e when e.NumValues > 0 ->
                    for i in 0..e.NumValues-1 do                                
                        let error = e.GetValueAsElement(i)
                        let field = error.GetElementAsString(FieldID)
                        let reason = error.GetElement(REASON)
                        let message = sprintf "Subscription Started w/errors( Field:   %s \n   Reason:   %s)" field (reason.GetElementAsString(DESCRIPTION))                                
                        yield (BBResponseType.Error, message)
                | _ ->                                
                    let message = sprintf "Subscription Started"         
                    yield (BBResponseType.Status, message)

            | SUBSCFAILURE ->
                match msg with
                | Element REASON reason ->
                    let desc = reason.GetElementAsString(DESCRIPTION)
                    let message = sprintf "Real-time Subscription Failure:    %s" desc                            
                    yield (BBResponseType.FatalError, message)   
                | _ ->
                    let message = sprintf "Subscription Failure:  (reason unknown) "                                                
                    yield (BBResponseType.FatalError, message)

            // There are probably more cases, processing them here
            | _ -> ()
    }

cmets 中的第 1、2 和 3 点来自另一个答案。我添加了第 0 点和第 4 点以使用活动模式来轻松进行模式匹配。

【讨论】:

  • 添加零点和四点(参数化活动模式)很棒。真正突出了主动模式在模式匹配中的强大功能!
  • 好吧,+50 代表的赏金非常值得!通过查看这段代码并将其与原始代码进行比较,我学到了很多东西。将整个方法按顺序包装似乎是一种很棒的技术。然而,我在我所拥有的任何一本书中都没有看到它。谢谢 Ankur 和 Pad!
  • @ChrisTarn:很高兴它有帮助。
猜你喜欢
  • 1970-01-01
  • 2017-08-20
  • 1970-01-01
  • 1970-01-01
  • 2013-10-19
  • 1970-01-01
  • 1970-01-01
  • 2019-08-20
  • 1970-01-01
相关资源
最近更新 更多