【问题标题】:Optional chaining in swift with arbitrary operations?使用任意操作快速进行可选链接?
【发布时间】:2014-06-17 19:03:27
【问题描述】:

Apple 提供了一个简洁的可选链示例

class Person {
  var residence: Residence?
}

class Residence {
  var numberOfRooms = 1
}

let john = Person()

if let roomCount = john.residence?.numberOfRooms {
  println("John's residence has \(roomCount) room(s).")
} else {
  println("Unable to retrieve the number of rooms.")
}

想象一下尝试通过一些算术运算来调整条件。这会导致编译器错误,因为模运算符不支持可选项。

if john.residence?.numberOfRooms % 2 == 0 { 
  // compiler error: Value of optional type Int? not unwrapped
  println("John has an even number of rooms")
} else {
  println("John has an odd number of rooms")
}

当然,您总是可以执行以下操作,但它缺乏可选链接的简单性和简洁性。

if let residence = john.residence {
  if residence.numberOfRooms % 2 == 0  {
    println("John has an even number of rooms")
  }else{
    println("John has an odd number of rooms")
  }
} else {
  println("John has an odd number of rooms")
}

是否有任何 Swift 语言功能可以提供更好的解决方案?

【问题讨论】:

    标签: ios swift chaining optional


    【解决方案1】:

    我认为您要查找的内容在函数式编程中通常称为monad

    它不能直接在 swift 中使用,但是通过使用一些语言特性,您可以以通用的方式自己实现 monad。 (并且还定义了一个漂亮的中缀运算符,使它看起来像 Haskell 中的 monad)

    在 Google 上快速搜索“monad swift”,在 https://gist.github.com/cobbal/7562875ab5bfc6f0aed6 处发现了一些看起来很有希望的代码

    【讨论】:

      【解决方案2】:

      模式匹配非常强大,可以在这样的情况下工作:

      switch john.residence?.numberOfRooms {
      case .Some(let x) where x % 2 == 0:
          println("Even number of rooms")
      default:
          println("Odd number of rooms")
      }
      

      【讨论】:

        【解决方案3】:

        如果我理解,您希望它说即使它肯定是偶数,如果它是奇数或未设置任何可选值,则它是奇数。如果是这样:

        if john.residence?.numberOfRooms && (john.residence!.numberOfRooms! % 2) == 0 {
            println("John has an even number of rooms")
        } else {
            println("John has an odd number of rooms")
        }
        

        是没有辅助函数我能做的最好的事情。问题是您需要方法来调用可选值以进行链接。

        使用可选链接时 if 和 Bool 存在错误,或者我不太了解它,但这对我有用:

        extension Int{
            func isEven() -> Bool { return self % 2 == 0 }
        }
        
        if (john.residence?.numberOfRooms?.isEven() == true){
            println("John has an even number of rooms")
        } else {
            println("John has an odd number of rooms")
        }
        

        如果你写

        if (john.residence?.numberOfRooms?.isEven())
        

        只要 numberOfRooms 有一些值,即使 isEven() 返回 false,它也会被解析为 true。好像表达式是 Bool?,但不是,它是 Bool。

        【讨论】:

        • numberOfRooms 不是可选的吗?这似乎又短又甜:if john.residence && john.residence!.numberOfRooms % 2 == 0
        • @JackWu 不错的解决方案。在某些情况下,您可能需要一个简洁的 Int 表达式?而不是布尔?例如,我找不到更好的方式来表达var numberOfWindows: Int? = john.residence.omap{$0.numberOfRooms * 2}
        【解决方案4】:

        我能想到的最好的方法是在 Optionals 上进行扩展,称为 omap(可选映射)。

        extension Optional{
          func omap<K:Any>(optionalBlock:(T)->(K?))->K?{
            if self{
              return optionalBlock(self!)
            }
            return nil
          }
        }
        

        与 map 类似,omap 只是将可选的实例返回给提供的闭包,但会适当地处理 nil 情况。

        这允许您将可选操作与任意操作链接起来,如下所示:

        if (john.residence.omap{r in r.numberOfRooms % 2 == 0}){
          println("John has an even number of rooms")
        } else {
          println("John has an odd number of rooms")
        }
        

        见:https://gist.github.com/Grantismo/3e1ba0412e911dfd7cfe

        【讨论】:

        • 我的印象是任何答案都应该包含在答案部分,即使它是由提问者提供的。请参阅:blog.stackoverflow.com/2011/07/…。我也不相信我的解决方案是最好的,所以我希望其他人能提供他们的想法。
        • 这还有效吗?扩展中的 T 是什么?我很难让它为自己工作。
        • 我只能让if (john.residence?.numberOfRooms.omap{$0 % 2 == 0}==true)编译并正常运行。
        • @LouFranco 我的例子是错误的,我已经相应地更新了它。 T 是 Optional 的类型。在此示例中,可选的是 Optional。所以if (john.residence.omap{$0.numberOfRooms % 2 == 0}) 有效
        【解决方案5】:

        一个可能的解决方案是使用 Guard

        func testOddEven(){
            guard let number = john.residence?.numberOfRooms else {print("Doesn't even have a residence loool"); return }
            switch number % 2 == 0 {
            case true:
                print("event")
                break;
            case false:
                print("odd")
                break;
            }
        }
        

        【讨论】:

          【解决方案6】:

          在过去的 10 分钟里,我一直在尝试做同样的事情,同时使用可选链接和 nil 合并运算符;编译器不喜欢它们中的任何一个。可以用 tf/then 来让它工作,但感觉很难看。

          应该可以对 Double 进行扩展,尽管我同意 swift 可以(应该?)支持标准运算符中的可选链接。以下在操场上工作:

          extension Double {
            func divide(by: Double) -> Double {
              return (self/by)
            }
          }
          
          var optDouble: Double? = nil
          var result = optDouble?.divide(by: 100.0)   // nil
          
          optDouble = 20.25
          result = optDouble?.divide(by: 100.0)       // 0.2025
          

          【讨论】:

            【解决方案7】:

            使以下行工作(即使运算符直接访问可选left操作数后面的值):

            if john.residence?.numberOfRooms % 2 == 0
            

            该运算符必须位于具有assignment: true 的优先级组中。模运算符在MultiplicationPrecedence优先组中:

            precedencegroup MultiplicationPrecedence {
              associativity:left
              higherThan: AdditionPrecedence
            }
            

            您可以使用assignment: true 创建自己的优先组

            precedencegroup AssignmentTruePrecedence {
                assignment: true
                associativity:left
                higherThan:AdditionPrecedence
                lowerThan:MultiplicationPrecedence
            }
            

            现在您可以声明自己的运算符,将其分配给上述优先组,并定义相关函数来进行模运算。

            infix operator %| : AssignmentTruePrecedence
            
            extension Int {
            
                static func %| (left: Int, right: Int)->Int {
                    return left % right
                }
            
            }
            

            使用我们的新模运算符,该行将正常工作:

            if john.residence?.numberOfRooms %| 2 == 0
            

            事实证明,您甚至不必声明新的运算符,因为重新声明模运算符并将其分配给我们的新优先级组似乎可行!

            infix operator % : AssignmentTruePrecedence
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2017-08-11
              • 2021-05-07
              • 1970-01-01
              • 2020-10-18
              • 1970-01-01
              • 1970-01-01
              • 2015-03-14
              • 2023-03-06
              相关资源
              最近更新 更多