swiftgenericsfunctional-programmingswift5function-object

How to invoke any swift function conditionally (without if block)


I want to write a general-purpose Swift function that serves the following simple purpose:

The purpose is to eliminate a lot of clumsy if statements in the code that meet a specific criteria.

Something like:

typealias ClosureType = (Any...) -> Any.  // Notice the variable argument of any kind

func invokeIfConditionIsTrue(closure: Closure, condition: Bool) {
    if condition {
         if let myFunc = closure as? ClosureType {
            myFunc()
            print("executed")
        } else {
            print("not executed")
        }
    }
}

func testIntToInt(i: Int) -> Int {
    return i*i
}

func testIntToDouble(i: Int) -> Double {
    return Double(i*i)
}


invokeIfConditionIsTrue(testIntToInt, true).       // executed     
invokeIfConditionIsTrue(testIntToDouble, false).   // not executed 

However, I am struggling to come up with syntax that will enable the argument passing to the input myFunc() func.

The example is pretty basic, and my input function closure could be accepting and emitting any type of input/outputs, including structs, classes and objective c stuff.

I have a hunch this is possible via a mechanism called function object, but I am not familiar enough with it.

Should I reinvent the wheel, or is there already a library/known way which is doing it successfully, and I am missing out?


Solution

  • I have no idea why you think

    invokeIfConditionIsTrue(testIntToInt, condition)
    

    is somehow superior to

    if condition { result = testIntToInt(n) }
    

    Or

    result = condition ? testIntToInt(n) : 0
    

    but what you want is pretty much impossible unless you wrap the function in a closure because there is no way to express "function with any arguments" in Swift as a type. The best you can do is wrap your function in a closure with known argument types. There's also no general Closure type that represents any closure.

    func invokeIfConditionIsTrue(closure: () -> (), condition: Bool) {
        if condition {
            closure()
            print("executed")
        }
    }
    
    invokeIfConditionIsTrue(closure: { result = testIntToInt(n) }, condition: true)
    

    But, as you can see, that's not really any better than an if statement. In fact, it's much worse.

    Another possibility is to define a function that returns a function, but it still needs to know the argument types.

    func invokeIfConditionIsTrue(closure: (Int) -> Int, condition: Bool) -> (Int) -> Int?
    {
        if condition {
            return closure
        }
        else
        {
            return { _ in 0 } // A dummy function
        }
    }
    
    invokeConditionIfTrue(closure: testIntToInt, condition: true)(n)