Избегайте последовательных объявлений "если пусть" в Свифте
В 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
}
Но быстрый компилятор не допускает этого.
Любое предложение?
Ответы
Ответ 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)?'
Любое предложение об обходном пути?