2016-01-27 3 views
6

Есть ли простой и определенный способ в 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 знает, что все они являются функциями, хотя таких типов данных нет. Я могу проверить, используя прочную подпись, но может быть ситуация, когда мне не нужна подпись * и просто хочу ее вызвать. Например:

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

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

Я могу переписать это так, что будет работать для 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 

* Согласитесь, не заботясь о подписи является грехом. Для аргументации давайте просто не заботимся о возвращаемом типе.

+0

Я думаю, проблема рассмотреть бы, что вы не только должны знать, если переменная была отозваны, но если он ожидает, что параметры. Знание того, что что-то вызываемое, не имеет значения, если вы не знаете его параметров. –

+0

Правда, следовательно, сноска. –

+0

Но я не говорю о типе возврата. Я говорю о параметрах. –

ответ

4

Вы можете проверить строковое представление .dynamicType вызываемого для существования подстроки ->. Не супер-элегантным, но это работает:

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 

Конечно, как Маркус Россель указует в комментариях выше, вы все равно ничего не о параметрах вызываемых знаете (но, возможно, это может быть следующим шагом чтобы узнать, учитывая, что вы знаете, что это можно назвать).


Добавление в отношении OPS вопросов ниже: только техническое обсуждение, а не рекомендованных методов.

Вы используете один и тот же подход, что и выше, чтобы проверить, если аргумент функции является замыканием без аргументов (() -> (...)) или один ни с аргументами, ни тип возвращаемого значения (() ->()), и так далее. Используя этот подход, вы можете определить общую функцию, вызывающую аргумент, присланный функции, только если он имеет определенный тип замыкания. Для этого «in-function-call» вам нужно будет использовать преобразование типа в ожидаемый тип закрытия, как описано выше в вашем 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 */ 
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) 
+0

Спасибо, я подумал о чем-то подобном, он просто чувствует себя немного взломанным, как вы упомянули. –

+0

Между, скажем, гипотетически мы выяснили, что у нас есть замыкание, которое не принимает параметров.Есть ли способ вызвать его, не зная тип возврата? Или нам все равно придется бросать его, как в последнем примере в вопросе? –

+0

@IanBytchek Я не уверен, что полностью понимаю, что вы хотите сделать, но: в первую очередь мы не _have_, чтобы использовать возвращаемый тип, поэтому для 'let b:() -> (Bool) = { return true} 'мы могли бы просто вызвать его и игнорировать возвращаемый тип, то есть' b() '. Однако, если вы имеете в виду вызов 'foo' в вышеприведенной функции (если _we_ знает, что это замыкание), было бы сложнее: компилятор ничего не знает о типе' foo' (даже если мы это делаем). Кроме того, поскольку замыкания являются не номинальными типами, мы не можем использовать альтернативный подход, в котором мы вводим ограничение некоторой функции только для закрытия нулевого параметра. – dfri

Смежные вопросы