Swift Function Fun Facts

You love Swift. You want to write a class that does HTTP, it might have methods like the following:

func get(URLString:String, params:[String:AnyObject],
    headers:[String:String])
func post(URLString:String, params:[String:AnyObject],
    headers:[String:String])
func put(URLString:String, params:[String:AnyObject],
    headers:[String:String])
// and more for HEAD, OPTIONS …

But you don't want to force your user to supply all arguments each time. You know that Swift supports default arguments, so you added some. Take GET as an example:

func get(URLString:String, params:[String:AnyObject]=[:],
    headers:[String:String]=[:])

Now users can do things like

{% highlight swift %} get("http://github.com") get("http://httpbin.org/get", headers:["Answer":42]) {% endhighlight %}

That's flexible! Woohoo!

After you thought about implementing these, though, you realize that HTTPMethod is merely a property on NSURLRequest. In other words, all of the previous methods can share the same implementation. In honor of the DRY principle, you write a function that accepts the method as an arguments and the previous functions each forwards the arguments to this function:

func impl(method:String, URLString:String,
    params:[String:AnyObject],
    headers:[String:String])
{
    // …
}

func get(URLString:String, params:[String:AnyObject]=[:],
    headers:[String:String]=[:])
{
    impl("GET", URLString:URLString, params:params,
        headers:headers)
}

func post(URLString:String, params:[String:AnyObject]=[:],
    headers:[String:String]=[:])
{
    impl("POST", URLString:URLString, params:params,
        headers:headers)
}

This seems like a sensible solution. Except that later you realize that there needs to be more parameters for each function, so in the end, each function looks like this:

func post(
    URLString             : String,
    params                : [String:AnyObject]       = [:],
    json                  : [String:AnyObject]?      = nil,
    headers               : [String:AnyObject]       = [:],
    auth                  : (String,String)?         = nil,
    allowRedirects        : Bool                     = true,
    requestBody           : NSData?                  = nil,
    URLQuery              : String?                  = nil,
    asyncCompletionHandler: ((HTTPResult!) -> Void)? = nil
    ) -> HTTPResult {
    return impl(
        "POST",
        URLString             : URLString,
        params                : params,
        json                  : json,
        headers               : headers,
        auth                  : auth,
        data                  : requestBody,
        URLQuery              : URLQuery,
        redirects             : allowRedirects,
        asyncCompletionHandler: asyncCompletionHandler
    )
}

Remembering that your goal is to respect DRY, and there are now giant blocks of code that all look the same except that first argument to impl(), you became determined to find a better alternative.

Well, why not give currying a try? This example of currying with Swift comes to your mind:

{% highlight swift %} func add(a:Int)(b:Int) -> Int { return a + b } let add3 = add(3) add3(b:2) // 5 {% endhighlight %}

If we apply this technique and treat method in impl() as a in the example, we would get:

func impl(method:String)(
    URLString:String,
    params:[String:AnyObject],
    headers:[String:String],
    …)
{
    // …
}
let get = impl("GET")
let post = impl("POST")

right? However, you are forcing users to supply each argument again. To make things worse, the number of arguments is a lot larger.

Hmm, but that's a solved problem, just add default values to impl()'s parameters:

func impl(method:String)(
    URLString:String,
    params:[String:AnyObject] = [:],
    headers:[String:String] = [:],
    …)
{
    // …
}

Ta-da! Wait a minute, Xcode now refuse to compile you code! Default argument is only permitted for a non-curried function parameter, it saids.

Stubborn as you are, you decide that perhaps the Swift team hasn't got around to implementing this feature for curry syntax yet. Functions are first-class citizens! Surely if you return a function with default arguments…?

func methodFactory(method:String)
    -> (params:[String:AnyObject] = [:],
        headers:[String:String] = [:], …)
    -> Void
{
    return {(params, headers, …) in
        impl(method, params:params, headers:headers, …)
    }
}

let get = methodFactory("GET")
let post = methodFactory("POST")

Turns out, this manual form of currying only works when default arguments aren't involved!

Now, you hate Swift.

(Just to be clear, I don't really hate Swift as in, uh, hate Swift. Judgning from some comments, I might have failed to convey the lightheartedness that I felt writing this up. It's really like saying to a friend "I hate you" after he/she pulls a prank on you.)