2014-10-10 10 views
5

Załóżmy, że mam funkcję, która zwraca opcjonalnie. nil jeśli błąd i wartość, jeśli sukces:swift, opcjonalne rozpakowanie, cofanie, jeśli warunek

func foo() -> Bar? { ... } 

mogę użyć poniższy kod do pracy z tej funkcji:

let fooResultOpt = foo() 
if let fooResult = fooResultOpt { 
    // continue correct operations here 
} else { 
    // handle error 
} 

Jednakże istnieje kilka problemów z tym podejściem do dowolnego kodu nietrywialne:

  1. Obsługa błędów wykonywana w końcu i łatwo jest coś przeoczyć. Znacznie lepiej, gdy kod obsługi błędów następuje po wywołaniu funkcji.

  2. Prawidłowy kod operacji jest wcięty o jeden poziom. Jeśli mamy inną funkcję do wywołania, musimy wciskać jeszcze raz.

zc jednym zazwyczaj mógł napisać coś takiego:

Bar *fooResult = foo(); 
if (fooResult == null) { 
    // handle error and return 
} 
// continue correct operations here 

Znalazłem dwa sposoby, aby osiągnąć podobny styl kodu ze Swifta, ale mi się nie podoba albo.

let fooResultOpt = foo() 
if fooResult == nil { 
    // handle error and return 
} 
// use fooResultOpt! from here 
let fooResult = fooResultOpt! // or define another variable 

Jeśli napiszę "!" wszędzie, po prostu wygląda źle dla mojego gustu. Mogę wprowadzić inną zmienną, ale to też nie wygląda dobrze. Idealnie chciałabym zobaczyć:

if !let fooResult = foo() { 
    // handle error and return 
} 
// fooResult has Bar type and can be used in the top level 

Czy brakuje czegoś w opisie lub jest jakiś inny sposób, aby napisać dobrą patrząc kod SWIFT?

Odpowiedz

2

Twoje założenia są poprawne - w Swift nie ma składni "negated if-let".

Podejrzewam, że jedną z przyczyn może być integralność gramatyczna. Przez cały Swift (i często w innych językach inspirowanych C), jeśli posiadasz instrukcję, która może wiązać symbole lokalne (np. Wymieniać nowe zmienne i podawać im wartości) i która może mieć treść bryły (np. Jeśli, podczas, dla), wiązania są dopasowane do wspomnianego bloku. Zezwalanie instrukcji blokowania na powiązanie symboli z otaczającym ją zakresem byłoby niespójne.

Nadal jednak należy pomyśleć - polecam filing a bug i zobaczę, co Apple robi w związku z tym.

2

To właśnie wzorzec dopasowania wszystkim chodzi, i to narzędzie przeznaczone dla tego zadania:

let x: String? = "Yes" 

switch x { 
case .Some(let value): 
    println("I have a value: \(value)") 
case .None: 
    println("I'm empty") 
} 

Formularz if-let tylko wygoda, gdy nie trzeba obie nogi.

+0

Dzięki, ale rozwiązanie nie różni się od składni if ​​(niech wartość = x) {. ..} else {...} i mam wszystkie wady, o których wspomniałem w pytaniu. – vbezhenar

1

Jeśli to, co piszesz, to zestaw funkcji wykonujących tę samą sekwencję transformacji, na przykład podczas przetwarzania wyniku zwracanego przez wywołanie REST (sprawdź, czy odpowiedź nie jest pusta, sprawdź stan, sprawdź błąd aplikacji/serwera, przeanalizuj Odpowiedź, itp.), co chciałbym zrobić, to stworzyć potok, który na każdym kroku transformuje dane wejściowe, a na końcu zwraca albo nil albo transformowany wynik określonego typu.

wybrałem operatora >>> niestandardowy, który wizualnie wskazuje przepływ danych, ale oczywiście nie krępuj się wybrać własne:

infix operator >>> { associativity left } 

func >>> <T, V> (params: T?, next: T -> V?) -> V? { 
    if let params = params { 
     return next(params) 
    } 
    return nil 
} 

Operator jest funkcją, która odbiera na wejściu wartość określonego typu i zamknięcie, które przekształca wartość na wartość innego typu. Jeśli wartość nie jest zerowa, funkcja wywołuje zamknięcie, przekazując wartość i zwraca jego wartość zwracaną. Jeśli wartość to nil, operator zwraca nil.

Przykładem jest prawdopodobnie potrzebne, więc załóżmy mam tablicę liczb całkowitych, a chcę, aby wykonać następujące operacje w kolejności:

  • suma wszystkich elementów tablicy
  • obliczyć moc 2
  • podzielić przez 5 i powrót część całkowitą, a pozostałą
  • suma powyższym 2 w numery wraz

Są to 4 funkcje:

func sumArray(array: [Int]?) -> Int? { 
    if let array = array { 
     return array.reduce(0, combine: +) 
    } 

    return nil 
} 

func powerOf2(num: Int?) -> Int? { 
    if let num = num { 
     return num * num 
    } 
    return nil 
} 

func module5(num: Int?) -> (Int, Int)? { 
    if let num = num { 
     return (num/5, num % 5) 
    } 
    return nil 
} 

func sum(params: (num1: Int, num2: Int)?) -> Int? { 
    if let params = params { 
     return params.num1 + params.num2 
    } 
    return nil 
} 

i to w jaki sposób używać:

let res: Int? = [1, 2, 3] >>> sumArray >>> powerOf2 >>> module5 >>> sum 

Wynikiem tego wyrażenia jest albo zero lub wartość typu, jak określono w ostatniej funkcji rurociągu, który w powyższym przykładzie to Int.

Jeśli trzeba zrobić lepszą obsługę błędów, można zdefiniować enum tak:

enum Result<T> { 
    case Value(T) 
    case Error(MyErrorType) 
} 

i zastąpić wszystkie ewentualne, w powyższych funkcji z Result<T>, wracając Result.Error() zamiast nil.

+0

Dzięki za odpowiedź, to jest dobre podejście, ale wydaje się bardzo nie imperatywne i trudne do rozszerzenia i modyfikacji w ogólnym przypadku. Może być bardzo odpowiedni w niektórych przypadkach, w których przepływ danych będzie naturalny. – vbezhenar

+0

Tak, to nie jest sposób ogólny, i pochodzi z programowania funkcjonalnego. Może to nie jest właściwe rozwiązanie twojego konkretnego problemu, ale mam nadzieję, że okaże się on przydatny w przyszłości ;-) – Antonio

1

Znalazłem sposób, który wygląda lepiej niż alternatywy, ale używa funkcji językowych w sposób niezalecany.

przykład za pomocą kodu z pytaniem:

let fooResult: Bar! = foo(); 
if fooResult == nil { 
    // handle error and return 
} 
// continue correct operations here 

fooResult może być stosowany jako normalnej zmiennej i nie jest potrzebne do korzystania z „?” lub "!" sufiksy.

dokumentacji Apple mówi:

Pośrednio nieopakowanych OPCJE są przydatne, gdy wartość opcjonalna jest potwierdzona natychmiast istnieć po raz pierwszy zdefiniowany jest opcjonalny i zdecydowanie można przyjąć istnieć w każdym punkcie później. Podstawowe użycie nieopakowanych opcji w Swift jest podczas inicjowania klasy, jak opisano w Unowned References i Implicitly Unwrapped Opcjonalne właściwości.

0

Jak o następujące elementy:

func foo(i:Int) ->Int? { 
    switch i { 
    case 0: return 0 
    case 1: return 1 
    default: return nil 
    } 
} 

var error:Int { 
    println("Error") 
    return 99 
} 

for i in 0...2 { 
    var bob:Int = foo(i) ?? error 
    println("\(i) produces \(bob)") 
} 

Wyniki w następujący wynik:

0 produces 0 
1 produces 1 
Error 
2 produces 99