Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check if variable is a block / function / callable in Swift

Tags:

swift

Is there a simple and definite way in Swift to check whether something is a callable block / function? In some languages it's a trivial thing, but perhaps I'm looking at this from a wrong perspective in Swift? Consider the following.

func foo(){ print("foo") }
var bar: () -> () = { print("bar") }
var baz: () -> (Bool) = { print("baz"); return true }

print(foo) // (Function)
print(bar) // (Function)
print(baz) // (Function)

print(foo is () -> ()) // true
print(bar is () -> ()) // true
print(baz is () -> ()) // false
print(baz is () -> (Bool)) // true

Swift knows that they are all functions, though there is no such data type. I can check by using a solid signature, but there might be a situation where I don't care about the signature* and simply want to invoke it. For example:

func call(callable: () -> ()) {
    callable()
}

call(foo) // foo
call(bar) // bar
call(baz) // error: cannot convert value of type '() -> (Bool)' to expected argument type '() -> ()'

I can rewrite it like this, which will work for Void and Bool return types, but doing this for every type is crazy, especially since I don't care about it, but compiler does…

func call(callable: Any) {
    if let block: () -> () = callable as? () -> () {
        block()
    } else if let block: () -> (Bool) = callable as? () -> (Bool) {
        block()
    }
}

call(foo) // foo
call(bar) // bar
call(baz) // truely baz

* Agree, not caring about the signature is a sin. For the argument sake let's just not care about the return type.

like image 941
Ian Bytchek Avatar asked Jan 27 '16 19:01

Ian Bytchek


People also ask

How to programmatically check if a variable type is INT in Swift?

How to programmatically check if Variable Type is Int in Swift? To check if a variable or object is an Int, use is operator as shown in the following expression. where x is a variable/object. The above expression returns a boolean value: true if the variable is an Int, or false if not an Int.

How do you assign a function to a variable in Swift?

As with any other type, you can leave it to Swift to infer the function type when you assign a function to a constant or variable: You can use a function type such as (Int, Int) -> Int as a parameter type for another function.

What is never type in Swift?

Swift defines a Never type, which indicates that a function or method doesn’t return to its caller. Functions and methods with the Never return type are called nonreturning. Nonreturning functions and methods either cause an irrecoverable error or begin a sequence of work that continues indefinitely.

What are the different types of statements in Swift?

There are three types of statements in Swift: 1. Simple Statements The simple statement consists of either an expression or declaration. For example, Here, var score = 9 * 5 is a statement that assigns the result of 9 * 5 to the score variable. Simple statements are the most common types of statements in Swift.


1 Answers

You can check the String representation of .dynamicType of the callable for existence of substring ->. Not super-elegant, but it works:

func isAClosure<T>(foo: T) -> Bool {
    return String(foo.dynamicType).containsString("->")
}

var a : () -> () = { print("Foobar") }
var b : (Double) -> (Bool) = { $0 > 0 }
var c : Int = 1

isAClosure(a) // true
isAClosure(b) // true
isAClosure(c) // false

Of course, as Marcus Rossel points out in the comment above, you still wouldn't know anything about the parameters of the callable (but perhaps that could be next step to find out, given that you know it's a callable).


Addition with regard to OPs questions below: just a technical discussion, and not recommended techniques.

You use the same approach as above to check if the function argument is a closure without arguments (() -> (...)) or one with neither arguments nor return type (() -> ()), and so on. Using this approach, you can define a generic function that call the argument sent to the function only if it is of a certain closure type. For this "in-function-call", you'll have to make use of type conversion to expected closure type, much as you've described in your Q above. It'll probably be difficult to circumvent this "non-generic" approach w.r.t. calling the closures. A few examples follow below.

/* Example functions */
func isAVoidParamClosure<T>(foo: T) -> Bool {
    let bar = String(foo.dynamicType).componentsSeparatedByString(" -> ")
    return bar.count > 1 && (bar.first?.characters.count ?? 0) == 2
}

func callIfVoidVoidClosure<T>(foo: T) {
    let bar = String(foo.dynamicType).componentsSeparatedByString(" -> ")
    if bar.count > 1 && !(bar.map{ $0 == "()" }.contains(false)) {
        if let foo = foo as? () -> () {
            foo()
        }
    }
}

func isASingleDoubleReturnTypeClosure<T>(foo: T) -> Bool {
    let bar = String(foo.dynamicType).componentsSeparatedByString(" -> ")
    return bar.count > 1 && bar[1] == "Double"
        /* rhs of '&&' lazily evaluated: [1] ok */
}

func printTwoTimesResultOfVoidDoubleClosure<T>(foo: T) {
    if isAVoidParamClosure(foo) && isASingleDoubleReturnTypeClosure(foo) {
        if let foo = foo as? () -> Double {
            let a: Double = 2*foo()
            print(a)
        }
    }
}

Example calls:

/* Example calls */
let a : () -> () = { print("Foobar") }
let b : (Double) -> (Bool) = { $0 > 0 }
let c : () -> Double = { 21.0 }
let d : Int = 1

isAVoidParamClosure(a) // true
isAVoidParamClosure(b) // false
isAVoidParamClosure(c) // true
isAVoidParamClosure(d) // false

callIfVoidVoidClosure(a) // Prints "Foobar"
callIfVoidVoidClosure(b)
callIfVoidVoidClosure(c)
callIfVoidVoidClosure(d)

printTwoTimesResultOfVoidDoubleClosure(a)
printTwoTimesResultOfVoidDoubleClosure(b) // Prints "42.0"
printTwoTimesResultOfVoidDoubleClosure(c)
printTwoTimesResultOfVoidDoubleClosure(d)
like image 79
dfrib Avatar answered Oct 09 '22 03:10

dfrib