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

Time:01-14

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

  • Take any function as argument
  • Take a Bool argument
  • If the bool argument is TRUE, invoke the input function with its args. Otherwise No-op.

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?

CodePudding user response:

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)

CodePudding user response:

Not sure why people downvoted my rather obvious question, or voted to close it. (thanks to everyone who contributed to my thought process)

After some haggling and searching for syntax (I wasn't good in FP, as I mentioned in the beginning), I was able to compile and run the below solution in my XCode 14.2 playground.

My desired function:

func executeIfCondition(function: (@escaping (Any...) -> Any), condition: Bool) {
    if condition {
        function()
    }
}

This was supposed to replace following types of calls across my codebase:

if condition {
    function()
}

Test functions whom I want to invoke, using executeIfCondition if condition = true.

func printStringAndReturnInt(i: Int, s: String) -> Int {
    print(s)
    return i
}

func printInt(i: Int) -> Void {
    print("\(i)")
}

func printArray(arr: [Int]) -> Void {
    arr.forEach({ print("\($0)") })
}

struct Struct1 {
    var value1: Int
    var value2: Int
}

func printStruct(t: Struct1) {
    print("Struct1: \(t.value1) - \(t.value2)")
}

class Class1 {
    var value1: Double = 100.0
    var value2: Double = 200.0
}

func printClass(c: Class1) {
    print("Class1: \(c.value1) - \(c.value2)")
}

Example Usage:

Instead of:

if (true) {
    printStringAndReturnInt(i: 5, s: "Wow!")
}

I will now use:

executeIfCondition(function: { _ in printStringAndReturnInt(i: 5, s: "Wow!") }, condition: true)

The rest:

executeIfCondition(function: { _ in printInt(i: 61) }, condition: false)
executeIfCondition(function: { _ in printArray(arr:[9,10,11,12]) }, condition: true)
executeIfCondition(function: { _ in printStruct(t: Struct1(value1: 100, value2: 200)) }, condition: true)
executeIfCondition(function: { _ in printClass(c: Class1()) }, condition: true)

This isn't exhaustive still, but enough for a start.

  • Related