Подтвердить что ты не робот

Избегайте последовательных объявлений "если пусть" в Свифте

В Swift я использовал объявления if let, чтобы проверить, не мой ли объект nil

if let obj = optionalObj
{
}

Но иногда мне приходится сталкиваться с последовательными объявлениями if let

if let obj = optionalObj
{
    if let a = obj.a
    {
        if let b = a.b
        {
            // do stuff
        }
    }
}

Я ищу способ избежать последовательных объявлений if let.

Я бы попробовал что-то вроде:

if let obj = optionalObj && if let a = obj.a && if let b = a.b
{
    // do stuff
}

Но быстрый компилятор не допускает этого.

Любое предложение?

4b9b3361

Ответ 1

Я немного написал эссе об альтернативах: https://gist.github.com/pyrtsa/77978129090f6114e9fb

Один из подходов, еще не упомянутый в других ответах, которые мне кажутся, заключается в добавлении множества перегруженных функций every:

func every<A, B>(a: A?, b: B?) -> (A, B)? {
    switch (a, b) {
        case let (.Some(a), .Some(b)): return .Some((a, b))
        default:                       return .None
    }
}

func every<A, B, C>(a: A?, b: B?, c: C?) -> (A, B, C)? {
    switch (a, b, c) {
        case let (.Some(a), .Some(b), .Some(c)): return .Some((a, b, c))
        default:                                 return .None
    }
}

// and so on...

Они могут использоваться в выражениях if let, выражениях case, а также optional.map(...) цепочки:

// 1.
var foo: Foo?
if let (name, phone) = every(parsedName, parsedPhone) {
    foo = ...
}

// 2.
switch every(parsedName, parsedPhone) {
    case let (name, phone): foo = ...
    default: foo = nil
}

// 3.
foo = every(parsedName, parsedPhone).map{name, phone in ...}

При добавлении перегрузок для every является шаблоном, но нужно только сделать это в библиотеке один раз. Аналогично, с помощью подхода Applicative Functor (т.е. Используя операторы <^> и <*>), вам нужно будет каким-то образом создать карриные функции, что также приведет к некоторому шаблону.

Ответ 2

Update

В swift 1.2 вы можете сделать

if let a = optA, let b = optB {
  doStuff(a, b)
}

Оригинальный ответ

В вашем конкретном случае вы можете использовать необязательную цепочку:

if let b = optionaObj?.a?.b {
  // do stuff
}

Теперь, если вам нужно сделать что-то вроде

if let a = optA {
  if let b = optB {
    doStuff(a, b)
  }
}

вам не повезло, так как вы не можете использовать дополнительную цепочку.

ТЛ; др

Вместо этого вы предпочитаете классный однострочный?

doStuff <^> optA <*> optB

Продолжайте читать. Ибо, насколько пугающе это может показаться, это действительно мощно и не так безумно использовать, как кажется.

К счастью, это проблема, которую легко решить с помощью подхода к функциональному программированию. Вы можете использовать абстракцию Applicative и предоставить метод apply для составления нескольких параметров вместе.

Вот пример, взятый из http://robots.thoughtbot.com/functional-swift-for-dealing-with-optional-values

Сначала нам нужна функция, чтобы применить функцию к необязательному значению только тогда, когда она содержит что-то

// this function is usually called fmap, and it represented by a <$> operator
// in many functional languages, but <$> is not allowed by swift syntax, so we'll
// use <^> instead
infix operator <^> { associativity left }

func <^><A, B>(f: A -> B, a: A?) -> B? {
    switch a {
    case .Some(let x): return f(x)
    case .None: return .None
    }
}

Затем мы можем скомпоновать несколько параметров с помощью apply, которые мы будем называть <*>, потому что мы классные (и мы знаем некоторые Haskell)

// <*> is the commonly-accepted symbol for apply
infix operator <*> { associativity left }

func <*><A, B>(f: (A -> B)?, a: A?) -> B? {
    switch f {
    case .Some(let value): return value <^> a
    case .None: return .None
    }
}

Теперь мы можем переписать наш пример

doStuff <^> optA <*> optB

Это будет работать, если doStuff находится в карри (см. ниже), то есть

func doStuff(a: A)(b: B) -> C { ... }

Результатом всего этого является необязательное значение, либо nil, либо результат doStuff

Вот полный пример, который вы можете попробовать на игровой площадке

func sum(a: Int)(b: Int) -> Int { return a + b }

let optA: Int? = 1
let optB: Int? = nil
let optC: Int? = 2

sum <^> optA <*> optB // nil
sum <^> optA <*> optC // Some 3

Как последнее замечание, очень просто преобразовать функцию в свою карриную форму. Например, если у вас есть функция с двумя параметрами:

func curry<A, B, C>(f: (A, B) -> C) -> A -> B -> C {
    return { a in { b in f(a,b) } }
}

Теперь вы можете выполнить любую двухпараметрическую функцию, например +

curry(+) <^> optA <*> optC // Some 3

Ответ 3

В некоторых случаях вы можете использовать необязательную цепочку. Для вашего простого примера:

if let b = optionalObj?.a?.b {
    // do stuff
}

Чтобы сохранить свое гнездование и дать себе одинаковые назначения переменных, вы также можете сделать это:

if optionalObj?.a?.b != nil {
    let obj = optionalObj!
    let a = obj.a!
    let b = a.b!
}

Ответ 4

После некоторой лекции спасибо Мартину Р, я нашел интересное обходное решение: fooobar.com/info/54746/...

func unwrap<T, U>(a:T?, b:U?, handler:((T, U) -> ())?) -> Bool {
    switch (a, b) {
    case let (.Some(a), .Some(b)):
        if handler != nil {
            handler!(a, b)
        }
        return true
    default:
        return false
    }
}

Решение интересное, но было бы лучше, если бы метод использовал вариационные параметры.

Я наивно начал создавать такой метод:

extension Array
{
    func find(includedElement: T -> Bool) -> Int?
    {
        for (idx, element) in enumerate(self)
        {
            if includedElement(element)
            {
                return idx
            }
        }

        return nil
    }
}

func unwrap<T>(handler:((T...) -> Void)?, a:T?...) -> Bool
{

    let b : [T!] = a.map { $0 ?? nil}

    if b.find({ $0 == nil }) == nil
    {
        handler(b)
    }
}

Но у меня есть эта ошибка с компилятором: Cannot convert the expression type '[T!]' to type '((T...) -> Void)?'

Любое предложение об обходном пути?