-2

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?

Nirav Bhatt
  • 6,940
  • 5
  • 45
  • 89
  • 1
    How about just accept a `() -> T`, and ask the caller to wrap whatever argument they have in a closure? e.g. `invokeIfConditionIsTrue(condition: true) { testIntToInt(i: 5) }`. – Sweeper Jan 13 '23 at 09:54
  • 2
    By the way, why ae if statements "clumsy"? – Sweeper Jan 13 '23 at 09:58
  • Those if statements require separate data structures for conditions. Eliminating them would put conditions in context. – Nirav Bhatt Jan 13 '23 at 10:04
  • 2
    Please explain in more detail. I'm not sure how eliminating something can put something in context, or how if statements can "require separate data structures". This also might be an [XY problem](http://xyproblem.info/). – Sweeper Jan 13 '23 at 10:10
  • Well, if statements themselves aren't the problem. But while we are eliminating the data structures that those if statements evaluate, it would be handy to eliminate them as well. They are thousands in number, and are clumsy in their verbosity. – Nirav Bhatt Jan 13 '23 at 10:29
  • Could you explain what underlying problem you’re trying to solve? There are some valid use cases for wrapping `if` statements (e.g. to track data dependencies in SwiftUI), but they’re handled by result builders. – Alexander Jan 13 '23 at 15:38

2 Answers2

1

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)
JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • Interesting. So it means that Swift doesn't have a way to use any of the objc-available runtime invocation info at all? – Nirav Bhatt Jan 13 '23 at 10:39
  • 1
    @NiravBhatt You mean `NSInvocation`? Well, you can use `NSInvocation` but only with Objective C derived objects. But closures work just as well. You just have to know your types at compile time. – JeremyP Jan 13 '23 at 10:42
  • I came across another interesting answer (https://stackoverflow.com/a/43439461/1506363) surrounding this. I do not understand it fully as I am still a beginner in FP. But I feel there seems some direction how this could be possible. – Nirav Bhatt Jan 13 '23 at 10:45
0

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.

E_net4
  • 27,810
  • 13
  • 101
  • 139
Nirav Bhatt
  • 6,940
  • 5
  • 45
  • 89