检查变量是否是Swift中的块/函数/可调用

时间:2022-01-10 16:56:06

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.

在Swift中是否有一种简单明确的方法来检查某些东西是否是可调用的块/函数?在某些语言中,这是一件微不足道的事情,但也许我从Swift的错误角度看待这个问题?考虑以下。

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:

Swift知道它们都是函数,尽管没有这样的数据类型。我可以使用可靠的签名进行检查,但可能存在我不关心签名*并且只是想调用它的情况。例如:

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…

我可以像这样重写它,它适用于Void和Bool返回类型,但是为每种类型执行此操作都很疯狂,特别是因为我不关心它,但编译器确实......

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.

*同意,不关心签名是罪。为了论证,我们不关心返回类型。

1 个解决方案

#1


4  

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

您可以检查可调用的.dynamicType的String表示是否存在substring - >。不是超级优雅,但它有效:

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).

当然,正如Marcus Rossel在上面的评论中指出的那样,你仍然不会知道关于可调用的参数的任何信息(但也许这可能是下一步找出,因为你知道它是可调用的)。


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

关于OPs问题的补充如下:仅仅是技术讨论,而不是推荐的技术。

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.

您使用与上面相同的方法来检查函数参数是否是没有参数的闭包(( - ) - >(...))或者既没有参数也没有返回类型(( - ) - >()),依此类推。使用此方法,您可以定义一个泛型函数,该函数只有在具有某种闭包类型时才调用发送给该函数的参数。对于这种“函数内调用”,您必须使用类型转换为预期的闭包类型,就像您在上面的Q中所描述的那样。这可能很难绕过这种“非通用”方法w.r.t.调用闭包。下面是一些例子。

/* 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)

#1


4  

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

您可以检查可调用的.dynamicType的String表示是否存在substring - >。不是超级优雅,但它有效:

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).

当然,正如Marcus Rossel在上面的评论中指出的那样,你仍然不会知道关于可调用的参数的任何信息(但也许这可能是下一步找出,因为你知道它是可调用的)。


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

关于OPs问题的补充如下:仅仅是技术讨论,而不是推荐的技术。

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.

您使用与上面相同的方法来检查函数参数是否是没有参数的闭包(( - ) - >(...))或者既没有参数也没有返回类型(( - ) - >()),依此类推。使用此方法,您可以定义一个泛型函数,该函数只有在具有某种闭包类型时才调用发送给该函数的参数。对于这种“函数内调用”,您必须使用类型转换为预期的闭包类型,就像您在上面的Q中所描述的那样。这可能很难绕过这种“非通用”方法w.r.t.调用闭包。下面是一些例子。

/* 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)