4

I'm using a 3rd party vendor's API in F#. On initialization the API returns a C# object that is nested msg container. It is populated with status messages and may include errors message. The vendor provides a C# sample parsing routine which I have ported F#.

The code sample loops through a nested msg container extracting fatal and nonfatal errors, and then return a List of tuples of type BBResponseType * string

Response Enum:

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

My port to F# looks like this:

 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

After I finished it, I looked at it and thought, "Wow, that's about as non-functional as you can get and still code in F#."

It does seem a lot clearer and succinct than the C# version, but I was thinking that there must be a better way to do all this without using so many loops and if/then's.

How can I do a better job of parsing these nested structures using pattern matching and recursion?

Andre P.
  • 613
  • 1
  • 7
  • 21

2 Answers2

6

Few pointers:

  1. Instead of returning a List of tuple return a seq of tuple - using the seq { } computation expression for creating sequence.
  2. Extract the if/else parts as a function of type Message -> (BBResponseType * string) and use this function inside the seq expression
  3. Inside this new function (which transforms the Message to tuple) use pattern matching to figure out what kind of (BBResponseType * string) to return.
Ankur
  • 33,367
  • 2
  • 46
  • 72
  • 1
    I like this answer, and up voted it, but as a total F# beginner a bit of code would be very helpul – Chris Tarn Aug 02 '13 at 09:55
  • @ChrisTarn: I agree, unfortunately I don't have F# environment and tryfsharp.org doesn't work on Linux :( – Ankur Aug 02 '13 at 10:04
3

To complement @Ankur's answer:

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
            | _ -> ()
    }

Point 1, 2 and 3 in comments are from the other answer. I added point 0 and 4 to use active patterns for easy pattern matching.

pad
  • 41,040
  • 7
  • 92
  • 166
  • Adding point zero and four (parameterized active pattern) was great. Really highlighted the power of active patterns for pattern matching! – Andre P. Aug 05 '13 at 12:37
  • Well the +50 rep bounty was well worth it! I learned so much from looking at this code and comparing it to the original. Wrapping the whole method in a sequence seems to be such a great technique. Yet I haven't seen it in any of the books I have. Thank you Ankur and Pad!! – Chris Tarn Aug 07 '13 at 04:58