Consequences of using Result to wrap functions with NSErrorPointer as a parameter?

51 views
Skip to first unread message

Sean Woodward

unread,
Dec 1, 2014, 4:49:44 PM12/1/14
to llam...@googlegroups.com
I'm looking at the concept of Either or a more specific Result<T,E> as a means of better error handling and flow on chained procedures. In doing so, it occurred to me to wrap functions that take an NSErrorPointer so they return a typed Result.

public func toResult<R>(f:(NSErrorPointer) -> R) -> () -> Result<R, NSError> {

   return {

       var err: NSError? = nil

       let r = f(&err)

       if err != nil {

           return Result.failure(err!)

       }

       

       return Result.success(r)

   }

}


public func toResult<A,R>(f:(A, NSErrorPointer) -> R) -> A -> Result<R, NSError> {

   return {

       var err: NSError? = nil

       let r = f($0, &err)

       if err != nil {

           return Result.failure(err!)

       }

       

       return Result.success(r)

   }

}


public func toResult<A,R>(f:(A, NSErrorPointer) -> R?) -> (A) -> Result<R, NSError> {

   return {

       var err: NSError? = nil

       if let r = f($0, &err) {

           return Result.success(r)

       }


        if err != nil {

           return Result.failure(err!)

       }

       

       assert(false, "Unable to cast result and no error")

   }

}


public func toResult<A,B,C>(f:(A, NSErrorPointer) -> B?) -> A -> Result<C, NSError> {

   return {

       var err: NSError? = nil

       if let r = f($0, &err) as? C {

           return Result.success(r)

       }

       

       if err != nil {

           return Result.failure(err!)

       }

       

       assert(false, "Unable to cast result and no error")

  }

}


This allows me to add an extension to a class like NSManagedObjectContext to provide specific wrappers.

extension NSManagedObjectContext {
    
    func executeFetchRequest<T>(request: NSFetchRequest) -> Result<[T], NSError> {
        let efr:(NSFetchRequest) -> Result<[T], NSError> = toResult(self.executeFetchRequest)
        return efr(request)
    }
    
    func countForFetchRequest(request: NSFetchRequest) -> Result<Int, NSError> {
        let cfr: (NSFetchRequest) -> Result<Int, NSError> = toResult(self.countForFetchRequest)
        return cfr(request)
    }
    
    func save() -> Result<Bool, NSError> {
        let s:() -> Result<Bool, NSError>= toResult(self.save)
        return s();
    }
}

Then I am able to use flatMap/bind to chain those functions in a process. I am interested to hear what the group thinks. Is it a good idea? Can you think of cases where this will introduce errors?

Rob Napier

unread,
Dec 2, 2014, 1:01:59 PM12/2/14
to llam...@googlegroups.com
The version of the same I'm currently exploring using an init (based on Rob Rix's work), along with a function called withError (based on RobR's try).

enum Result<T,E> {

  case Success(Box<T>)

  case Failure(Box<E>)


  ...

  init(_ x: T?, failWith: E) {    switch x {

    case .Some(let v): self = Success(Box(v))

    case .None: self = Failure(Box(failWith))

    }

  }

}


func withError<T>(f: NSErrorPointer -> T?, file: String = __FILE__, line: Int = __LINE__) -> Result<T, NSError> {

  var error: NSError?

  return f(&error).map(success) ?? failure(error ?? mkError(""))

}



Here's a full example of what it looks like in code:

func URLForSearch(search: String) -> Result<NSURL, NSError> {

  return success(search)

    .flatMap { Result(

      $0.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding),

      failWith: mkError("Malformed Search: \($0)")) }

    .flatMap { Result(NSURL(string: queryBase + $0),

      failWith: mkError("Malformed URL: \($0)"))}

}


func DataForURL(url: NSURL) -> Result<NSData, NSError> {

  return withError { error in

    NSURLConnection.sendSynchronousRequest(NSURLRequest(URL: url),

      returningResponse: nil, error: error)}

}


func JSONForData(data: NSData) -> Result<AnyObject, NSError> {

  return withError { error in

    NSJSONSerialization.JSONObjectWithData(data,

      options: NSJSONReadingOptions(0), error: error)}

}


func ParseJSON(json: AnyObject) -> Result<[String], NSError> {

  return

    Result(json as? [AnyObject],

      failWith: mkError("Expected array. Received: \(json)"))


      .flatMap { Result($0.count == 2 ? $0 : nil,

        failWith: mkError("Array incorrect size: \($0)"))}


      .flatMap { Result($0[1] as? [String],

        failWith: mkError("Malformed array: \($0)"))}

}


func pagesForSearch(search: String) -> Result<[String], NSError> {

  return URLForSearch(search)

    .flatMap(DataForURL)

    .flatMap(JSONForData)

    .flatMap(ParseJSON)

}



--
You received this message because you are subscribed to the Google Groups "llamakit" group.
To unsubscribe from this group and stop receiving emails from it, send an email to llamakit+u...@googlegroups.com.
To post to this group, send email to llam...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/llamakit/0c579522-6390-4ead-829b-0c7c9035ccc8%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Rob Napier
Cocoaphony blog -- http://robnapier.net/blog
iOS Programming Pushing the Limits -- http://robnapier.net/book

Rob Napier

unread,
Dec 2, 2014, 1:03:09 PM12/2/14
to llam...@googlegroups.com
Note that "mkError" here is a bit sloppy, and not intended as the final function. It's how I was breaking through some compiler hang-ups if you make too many default parameters.

-Rob
Reply all
Reply to author
Forward
0 new messages