【问题标题】:What aren't these two ways of expressing map function equivalent?这两种表达地图功能的方式不是等效的吗?
【发布时间】:2021-03-22 22:43:24
【问题描述】:

我今天在看另一个 SO 问题时得到了一个惊喜:

let s = "1,a"
let arr = s.split(separator: ",")
let result = arr.compactMap{Int($0)} // ok
let result2 = arr.compactMap(Int.init) // error

为什么第 3 行合法而第 4 行不合法?我原以为“如果可能,将传入参数强制为 Int”这两种说法是完全等价的。

我知道第 4 行在 Subsequence 上卡住了,我知道如何摆脱困境:

let result2 = arr.map(String.init).compactMap(Int.init) // ok

我不明白为什么他们不会以同样的方式窒息。

【问题讨论】:

  • .init 真的初始化了吗?
  • @aheze Int(...) Int.init。一个是电话,另一个是名字。用括号我们提供了一个函数的名称,这就是我正在做的事情。问题是,为什么这与我在花括号中使用的函数不完全相同。正如我所说,我可以旋转第 4 行来让它工作,我只是不明白为什么我必须这样做。
  • @matt,在评论之前没有注意到您的编辑 :)
  • 这两个答案说明了原因。我发现有两个地方在我想使用时经常无法使用.init,因为缺少重载。默认参数是一个,你在这里遇到了。另一个是自动关闭:stackoverflow.com/questions/34699576/…
  • @Jessy 我仍然觉得在语言上很奇怪Int() 本身并不是Int.init 的语法糖。我猜是因为函数引用的逻辑和语法不同;它必须是明确的。

标签: swift map-function


【解决方案1】:

看起来接受SubstringInt.init 重载具有以下签名:

public init?<S>(_ text: S, radix: Int = 10) where S : StringProtocol

所以,Int($0) 有效,因为它使用默认的 radix,但没有接受 SubstringInt.init(_:) - 只有 Int.init(_:radix:) 可以接受 - 所以它失败了。

如果有的话:

extension Int {
    public init?<S>(_ text: S) where S : StringProtocol {
        self.init(text, radix: 10)
    }
}

那么这将起作用:

let result1 = arr.compactMap(Int.init)

【讨论】:

    【解决方案2】:

    实际上第一个版本(Int($0))调用了这个初始化器,它有两个参数(其中一个有默认值):

    @inlinable public init?<S>(_ text: S, radix: Int = 10) where S : StringProtocol
    

    如果我像这样定义自定义初始化程序,那么第二个示例也可以。

    extension Int {
        init?<S>(_ string: S) where S: StringProtocol {
            // convert somehow, e.g: self.init(string, radix: 10)
            return nil
        }
    }
    
    let result2 = arr.compactMap(Int.init)
    

    在我看来,如果我在compactMap 中写Int.init,它只能调用确切的初始化程序(或函数),并且无法推断第一个调用的初始化程序的第二个参数。 em>

    另一个例子:

    func test1<S>(param1: S) -> String where S: StringProtocol {
        return ""
    }
    
    func test2<S>(param1: S, defaultParam: String = "") -> String where S: StringProtocol {
        return ""
    }
    
    extension Sequence {
        func customCompactMap<ElementOfResult>(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult] {
            compactMap(transform)
        }
    }
    
    arr.customCompactMap(test1)
    arr.customCompactMap(test2) // error
    

    我认为函数引用不能保存任何默认值。不幸的是,我没有找到任何官方参考,但似乎很有趣。

    证明,最后一个例子:

    func test3(param1: String, defaultParam: String = "") { }
    let functionReference = test3
    functionReference("", "")
    functionReference("") // error
    

    这里的functionReference's 类型是(String, String) -&gt; (),尽管test3 函数的第二个参数有一个默认值。如您所见,functionReference 不能只用一个值调用。

    【讨论】:

      【解决方案3】:

      我尝试查找 Swift 论坛帖子,其中核心团队的某个人对此进行了解释,但抱歉,我找不到。您可以去那里询问并澄清这一点:

      默认参数实际上不会产生重载。

      相反,在调用站点使用默认参数是使用所有参数的语法糖。编译器会为您不使用的插入默认值。

      一些结果……


      您不能将具有默认参数的函数用作具有简化签名的闭包。正如您在问题中所展示的那样,您必须将它们包装在新的闭包中。

      func ƒ(_: Int = 0) { }
      
      let intToVoid: (Int) -> Void = ƒ // compiles
      
      // Cannot convert value of type '(Int) -> ()' to specified type '() -> Void'
      let voidToVoid: () -> Void = ƒ
      

      具有不同默认参数模式的方法,在调用站点看起来相同,不被视为覆盖。

      class Base {
        func ƒ(_: Any? = nil) -> String { "Base" }
      }
      
      final class Derived: Base {
        // No `override` required.
        func ƒ() -> String { "Derived" }
      }
      
      Base().ƒ() // "Base"
      Derived().ƒ() // "Derived"
      (Derived().ƒ as (Any?) -> String)("argument") // "Base"
      

      默认参数不允许满足协议要求。

      protocol Protocol {
        func ƒ() -> String
      }
      
      // Type 'Base' does not conform to protocol 'Protocol'
      extension Base: Protocol { }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-29
        • 1970-01-01
        • 2011-04-20
        • 2012-11-30
        • 2023-04-04
        • 1970-01-01
        相关资源
        最近更新 更多