Swift currying in practice

I was converting some code from Objc to Swift and I came along an interesting use of functional currying in swift. (Playground can be found here).

In this application, I am connecting to a server that implements a session expiry mechanism; All of the requests to the server might return a session expiry error. If I receive this error, I have to refresh the session (passing the username and password) and retry the previous server connection again.

As an example, fetching posts from a server is implemented as a function that takes the user id and the completion closure as it’s parameters. The fetch posts function might look like this:

typealias CompletionClousure = ([Post]?, ResponseError?) -> ()
func fetchPosts(userId: String, completion: CompletionClousure) {
// Fetch the posts using NSURLSession or anything else
// Call the completion closure with the result
}

Since I am going to reuse the same closure definition again, I created the type alias CompletionClousure to improve readability.

The ResponseError is an enum that contains all the different error codes that the server might return:

enum ResponseError: ErrorType {
case SessionExpired
case OtherKindOfError
}

When the response error is .SessionExpired I need to refresh the session and call fetchPosts again. Refresh session is done calling this function:

func refreshSession(completion: () -> () ) {
// Refresh the session using NSURLSession or anything else
// Use the current username and password to refresh the session
}

First implementation: wrapping the function

My first attempt is to create a wrapper function around fetchPosts. This wrapper function would encapsulate the “Connect to server and refresh session if needed” flow:

func fetchPostsAndRefreshSessionIfNeeded(userId: String, completion: CompletionClousure) {
fetchPosts(userId) { (posts, error) in
    if let error = error where error == .SessionExpired {
      refreshSession {
print("Refreshing Session")
fetchPosts(userId) { posts, error in
          // Display the posts
completion(posts, nil)
}
}
return
}
    // Display the posts
completion(posts, nil)
}
}

That is not too bad, the fetchPostsAndRefreshSessionIfNeeded encapsulates the refresh session nicely. Fetching posts now is done using fetchPostsAndRefreshSessionIfNeeded instead of fetchPosts:

fetchPostsAndRefreshSessionIfNeeded("123") { posts, error in
print("Use posts to fill UI")
}

The main problem with the above implementation is that we have to replicate the same code for each server request that we have. That means we need to create fetchCommentsAndRefreshSessionIfNeeded, fetchFriendsAndRefreshSessionIfNeeded and so on…

Currying to the rescue

We can however use functional currying to help us reuse the main algorithm from above. The idea is to update fetchPostsAndRefreshSessionIfNeeded and make it reusable with all other kind of server requests.

Lets analyse fetchPostsAndRefreshSessionIfNeeded signature:

func fetchPostsAndRefreshSessionIfNeeded(userId: String, completion: CompletionClousure)

This function takes the userdId as its parameter, it then calls fetchPosts in its implementation. Other than these two reasons, fetchPostsAndRefreshSessionIfNeeded main flow will be the same for all other requests.

We really want to have a function that is able to perform any server request. This function encapsulates the idea of refreshing a session and call the original request again if needed. Lets consider the following implementation:

func requestAndRefreshIfNeeded(request: CompletionClousure -> (), completion: CompletionClousure)
completion: (T, ResponseError?) -> ()) {
    request { (posts, error) in
      if let error = error where error == .SessionExpired {
        refreshSession {
print("Refreshing Session")
request { (posts, error) in
            // Display the posts
completion(posts, error)
}
}
return
}
      // Display the posts
completion(posts, error)
}
}

Yes, there are too many brackets and arrows. Yet, the implementation of requestAndRefreshIfNeeded is is really similar to fetchPostsAndRefreshSessionIfNeeded, the difference is that requestAndRefreshIfNeeded is not coupled to a specific request.

requestAndRefreshIfNeeded takes in two parameters:

  • request: The actual request function, this function only takes 1 parameter, the CompletionClousure and returns void.
  • completion: The completion closure to call.

Next, we need to convert our fetchPosts from taking 2 parameters to taking 1 only. This is where currying can help us. We can define a curried version of fetchPosts:

func curriedFetchPosts(userId: String)(completion: CompletionClousure) {
fetchPosts(userId, completion: completion)
}

Now that we have a curried fetch posts, we can call its parameter separated:

//Passing first argument
let curried = curriedFetchPosts(userId: "123")
//Passing second argument
curried { posts, error in
print("Use posts to fill UI")
}

Calling ‘curriedFetchPosts(userId: “123”)’ returns a partially applied function. This partially applied function is still a function of type ‘CompletionClousure -> ()’, that is the same type that requestAndRefreshIfNeeded expects.

Lets used this curried version of fetch posts with requestAndRefreshIfNeeded:

requestAndRefreshIfNeeded(curriedFetchPost(userId: "123")) { (posts, error) in
print("Use posts to fill UI")
}

As expected, we can reuse requestAndRefreshIfNeeded with any other server request we have. For example, if we have a fetchComments function, we can call it as following:

func fetchComments(commentId: String, completion: ([Comment]?, ResponseError?) -> ()) {
completion([Comment()], nil)
}
// Create a curried version of fetchComments
func curriedFetchComments(commentId: String)(completion: ([Comment]?, ResponseError?) -> ()) {
fetchComments(commentId, completion: completion)
}

Next, we use ‘curriedFetchComments(“123”)’ as a parameter to requestAndRefreshIfNeeded:

requestAndRefreshIfNeeded(curriedFetchComments("123")) { (comments: [Comment]?, error) -> () in
print("Comments fetched")
}

Note about generics

The above implementation of requestAndRefreshIfNeeded is still not generic since the CompletionClosure is tied to [Post] array.

I used CompletionClousure since its easier to follow. However, in order to make requestAndRefreshIfNeeded generic we need to replace the CompletionClousure type alias with the generic version. The final version of requestAndRefreshIfNeeded looks like the following:

func requestAndRefreshIfNeeded<T>(request: ((T, ResponseError?) -> ()) -> (), completion: (T, ResponseError?) -> ()) {
    request { (posts, error) in
      if let error = error where error == .SessionExpired {
        refreshSession {
print("Refreshing Session")
request { (posts, error) in
            // Display the posts
completion(posts, error)
}
}
return
}
      // Display the posts
completion(posts, error)
}
}

Conclusion

We saw how functional currying helped making the code more reusable and easier to read. I have uploaded a playground that contains all the samples in this post. Please find it here.


As always for notes and followup you can find me here @ifnotrue