2016-01-27 9 views
6

Czy w Swift istnieje prosty i określony sposób sprawdzenia, czy coś jest blokowalnym blokiem/funkcją? W niektórych językach jest to rzecz trywialna, ale może patrzę na to z niewłaściwej perspektywy w Swift? Rozważ następujące.Sprawdź, czy zmienna jest blokiem/funkcją/wywoływaną w 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 wie, że wszystkie są funkcjami, chociaż nie istnieje taki typ danych. Mogę sprawdzić za pomocą stałego podpisu, ale może być sytuacja, w której nie interesuje mnie podpis * i po prostu chcę go wywołać. Na przykład:

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

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

mogę przepisać go w ten sposób, który będzie pracować dla Void i Bool rodzajów powrotów, ale robi to dla każdego typu jest szalony, zwłaszcza, że ​​nie dbam o to, ale kompilator robi ...

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 

* Zgadzam się, nie dbając o podpisie jest grzechem. Dla argumentu nie zwracajmy uwagi na typ zwrotu.

+0

Chyba problem do rozważenia byłoby, że nie tylko trzeba wiedzieć, jeśli zmienna była wymagalne, ale jeśli oczekuje parametrów. Świadomość, że coś można wywołać, nie ma żadnej wartości, jeśli nie znasz jego parametrów. –

+0

To prawda, stąd przypis. –

+0

Ale nie mówię o typie zwrotu. Mówię o parametrach. –

Odpowiedz

4

Można sprawdzić reprezentację łańcuchową .dynamicType żądanej wartości dla podłańcucha ->. Nie super-eleganckie, ale działa:

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 

Oczywiście, jak Marcus Rossel zauważa w komentarzu powyżej, nadal nie będzie nic o parametrach wywoływalnym wiem (ale być może, że może być następny krok dowiedzieć się, biorąc pod uwagę, że wiesz, że jest to wywołanie).


dodatku w odniesieniu do OPS poniższe pytania: po prostu dyskusji technicznych, a nie zalecanych technik.

użyć tego samego podejścia jak powyżej, aby sprawdzić, czy argumentem funkcji jest zamknięcie bez argumentów (() -> (...)) lub jednym z ani argumentów, ani typ (() ->()) powrót, i tak dalej. Za pomocą tego podejścia można zdefiniować funkcję ogólną, która wywołuje argument wysłany do funkcji tylko wtedy, gdy ma określony typ zamknięcia. Dla tego "wywołania w funkcji", będziesz musiał użyć konwersji typu do oczekiwanego typu zamknięcia, tak jak opisałeś w swoim Q powyżej. Prawdopodobnie trudno będzie ominąć to "nietypowe" podejście w.r.t. Wywołanie zamknięcia. Poniżej kilka przykładów.

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

Przykład połączeń:

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

Dzięki, pomyślałem o czymś takim, to po prostu trochę odurza, jak wspomniałeś. –

+0

Pomiędzy, powiedzmy hipotetycznie, dowiedzieliśmy się, że mamy zamknięcie, które nie przyjmuje parametrów.Czy istnieje sposób na wywołanie go bez znajomości typu zwrotu? Czy nadal musimy go rzucać tak, jak w ostatnim przykładzie w pytaniu? –

+0

@IanBytchek Nie jestem pewien, czy w pełni rozumiem, co chcesz zrobić, ale: po pierwsze, nie możemy _ użyć typu zwrotu, więc dla 'let b:() -> (Bool) = { return true} 'możemy po prostu wywołać i zignorować typ zwracany, tzn.' b() '. Jeśli jednak powołujesz się na wywołanie 'foo' w powyższej funkcji (jeśli _we_ wiemy, że to zamknięcie), to byłoby to trudniejsze: kompilator nie zna nic na temat' foo' (nawet jeśli robimy). Ponadto, ponieważ zamknięcia są typami nie nominalnymi, nie możemy użyć potencjalnie alternatywnego podejścia, w którym ograniczymy jakąś funkcję tylko do zamykania zerowego parametru. – dfri

Powiązane problemy