1
votes

I am pretty new in f# world. I wrote a very small application that query data from sap and show the result as output. When the application try to connect sap, it could throw some exceptions, in case something goes wrong.

Look at following code:

type Customer() =
    let mutable _lastName = String.Empty
    member self.LastName with get () = _lastName

    member self.QueryData () =
        //Some CODES here

        let bapi = SapBapi()
        let bapiFunc = bapi.GetBapiFunc(dest, "BAPI_CUSTOMER_GETDETAIL1")
        match bapiFunc with
        | Success bp  ->
            //Some CODES here

            let addressData = bp.GetStructure("PE_PERSONALDATA")
            _lastName <- addressData.GetString("LASTNAME")
            None 
        | RfcCommunication ex ->
            Some(ex :> Exception)
        | RfcLogon ex ->
            Some(ex :> Exception)
        | RfcAbapRuntime ex ->
            Some(ex :> Exception)

As you can see, I handle the error with option type and downcast the throwed exception to base exception type.

In the main function

open CustomerBapi
open System

[<EntryPoint>]
let main argv = 

    let customer = CustomerBapi.Customer()
    let ex = customer.QueryData()

    match ex with 
    | Some ex ->
        printfn "%s" ex.Message
    | None ->
        printfn "%s" customer.LastName

    Console.ReadLine() |> ignore
    0 // return an integer exit code

This code works but do I handle exception in the right way?

I read an article in internet, that handling exception in f# should return an error code, it's more easy then the exception style.

2
You may want to read Scott Wlaschin's excellent article Railway oriented programming for an introduction to how to model errors in a functional way with F#. - Mark Seemann
Without access to the article saying one thing it is hard to refute its arguments. IMO Exceptions, unlike any kind return value (however wrapped in option or discriminated union types), allow intermediate code between the point of failure and the point of handling to be much cleaner. In real applications this intermediate code is in the majority. In the end this is a matter of opinion. - Richard
Richard: this is true, however a function that terminates with an exception is side-effecting, and you don't want that if you're striving to write F# in a purely functional way. - scrwtp

2 Answers

2
votes

A typical way of handling errors within the type system is to employ an Either type.

 type Either<'a,'b> =
     | Left of 'a
     | Right of 'b

Conventionally Right value carries the success result and Left carries an error or exception (either as a string or an exc type). A simple way to think about it is to treat it like an option where Right corresponds to the Some case and instead of a None you have error information.

So your code could become:

// QueryData no longer needs to depend on side effects to work, 
//so you can make it a regular function instead of a method
let result = queryData()

match result with 
| Left ex ->
    // handle exception
    printfn "%s" ex.Message
| Right result ->
    // either set the property, or make customer a record
    // and set the name field here
    customer.LastName <- result
    printfn "%s" customer.LastName

The bit about error codes sounds very wrong, would like to know where you found it.

0
votes

In general I think that your solution is okay, but can be improved.
You mix a bit the functional and OO style in your code. It feels a bit strange to me that you are working with the exception as the only optional value. Usually the customer should be the value which has the optionality included and the match should be if the customer has a value or not.