Heath Borders
Heath Borders

Reputation: 32117

Curry all swift function parameters, but don't call the function

I have the following swift function:

func foo(bar: String)(_ baz: String) {
  NSLog("bar \(bar), baz: \(baz)")
}

let f = foo("fizz")("buzz") // won't compile, foo returns Void

I want to pass that to dispatch_async, but I can't because I can't curry both parameters without invoking the function. How do I curry both bar and baz without calling the foo?

Upvotes: 0

Views: 183

Answers (2)

Lily Ballard
Lily Ballard

Reputation: 185681

If you are free to change the function declaration, Heath's answer is correct. If you don't want to do that, then you just need to use an inline closure, e.g.

// pre-existing foo definition
func foo(bar: String)(_ baz: String) {
    NSLog("bar \(bar), baz: \(baz)")
}

let f = { foo("fizz")("buzz") }
f()

If your problem is you want to evaluate the arguments immediately, you can use a capture list:

let f = { [a=bar(),b=baz()] in foo(a)(b) }

Or when written as a call to dispatch_async:

dispatch_async(queue) { [a=bar(),b=baz()] in
    foo(a)(b)
}

This works because the capture list is evaluated immediately by the caller when the closure is created (as opposed to being evaluated when the closure is invoked).

Another option is to define a nested curried function. Nested functions are really just sugar for closures, but they can be more convenient:

/// Function that your code is executing in
func myFunc() {
    // ...

    // define a nested function
    func f(bar: String, baz: String)() {
        foo(bar)(baz)
    }

    // now call it
    dispatch_async(dispatch_get_main_queue(), f("foo", "bar"))
}

Addendum: I strongly recommend against saying NSLog("bar \(bar), baz: \(baz)"). NSLog takes a format string and arguments, and so if either bar or baz contain anything that looks like a format token (e.g. %@ or %d) then the call to NSLog() will behave badly and possibly crash. You have two reasonable options:

  1. Say NSLog("%@", "bar \(bar), baz: \(baz)")
  2. Say NSLog("bar %@, baz: %@", bar, baz)

The best option depends on whether your arguments are already compatible with CVarArg (which String is but e.g. String? is not). Also note that if your arguments are guaranteed to not contain format tokens, such as if they're numbers or booleans, then you can go ahead and use your existing NSLog("bar \(someIntVar) baz \(someBoolVar)") style.

Upvotes: 5

Heath Borders
Heath Borders

Reputation: 32117

Append an extra set of parens at the end of the function declaration:

func foo(bar: String)(baz: String)() {
  NSLog("bar \(bar), baz: \(baz)")
}

let f = foo("fizz")("buzz") // compiles!
f() // logs: "bar fizz, baz: buzz"

Upvotes: 3

Related Questions