Swift 5.2 将实例作为函数调用

2020-03-19 20:44:41 浏览数 (1)

Swift 5.2中的一个新功能是可以将类型实例作为函数调用(callAsFunction)。或者,如Swift Evolution 提案所述,“用户定义的标称类型的可调用值”。此函数的简短描述是,它允许您调用实现了callAsFunction方法的任何类型的实例,就好像它是一个函数一样

callAsFunction

例如一个计算每年财富值以8%递增的计算器,传递一个初始值以及经过多少年,得出最终的财富:

代码语言:javascript复制
struct InvestmentsCalculator {
  let input: Double
  let averageGrowthPerYear = 0.08

  func callAsFunction(years: Int) -> Double {
    return (0..<years).reduce(input, { value, _ in
      return value * (1   averageGrowthPerYear)
    })
  }
}

let calculator = InvestmentsCalculator(input: 1000)
let newValue = calculator(years: 10)

实现了callAsFunction方法后,可以直接将实例当做函数使用,他的具体实现就是callAsFunction的实现。

尽管这很酷,但您可能更想知道这样的功能在实际编程过程中何时有用,以及如何在代码中应用它。

Swift不是唯一允许其用户调用某些类型的实例作为函数的语言,比如:

  • Python:object.__call__(self[, args...])
  • C :operator() (function call operator)
  • Scala: def apply(...) (apply methods)
它是 @dynamicCallable 的静态形式

SE-0216引入了用户定义的动态可调用值。在考虑的可替代方案部分中,要求我们与提议的动态版本一起设计和实现该提议的“静态可调用”版本。有关“静态可调用项”的讨论,请参照pitch thread。

在Swift中使用callAsFunction相对简单。任何定义callAsFunction方法的对象都可以视为一个函数。您的callAsFunction可以接受参数并返回值,如Swift Evolution建议中所示,并带有以下示例:

代码语言:javascript复制
struct Adder {
  let base: Int

  func callAsFunction(_ x: Int) -> Int {
    return base   x
  }
}

let add3 = Adder(base: 3)
add3(10) // 13

您甚至可以在一个对象上有多个重载:

代码语言:javascript复制
struct Adder {
    var base: Int

    func callAsFunction(_ x: Int) -> Int {
        return base   x
    }

    func callAsFunction(_ x: Float) -> Float {
        return Float(base)   x
    }

    func callAsFunction<T>(_ x: T, bang: Bool) throws -> T where T: BinaryInteger {
        if bang {
            return T(Int(exactly: x)!   base)
        } else {
            return T(Int(truncatingIfNeeded: x)   base)
        }
    }
}

let add1 = Adder(base: 1)
add1(2) // => 3
try add1(4, bang: true) // => 5

当类型检查失败时,错误消息看起来类似于函数调用的错误消息。如有歧义,编译器将显示相关的callAsFunction方法候选对象。

代码语言:javascript复制
add1("foo")
// error: cannot invoke 'add1' with an argument list of type '(String)'
// note: overloads for functions named 'callAsFunction' exist with these partially matching parameter lists: (Float), (Int)
add1(1, 2, 3)
// error: cannot invoke 'add1' with an argument list of type '(Int, Int, Int)'
直接引用callAsFunction

由于callAsFunction方法是一种普通方法,因此可以使用其声明名称引用callAsFunction方法,并获取捕获self的闭包。这正是今天方法引用的工作方式。

代码语言:javascript复制
let add1 = Adder(base: 1)
let f1: (Int) -> Int = add1.callAsFunction
let f2: (Float) -> Float = add1.callAsFunction(_:)
let f3: (Int, Bool) throws -> Int = add1.callAsFunction(_:bang:)

当类型也是@dynamicCallable时

类型既可以具有callAsFunction方法,也可以使用@dynamicCallable声明。在对调用表达式进行类型检查时,类型检查器将首先尝试将调用解析为函数或初始化程序调用,然后将其解析为callAsFunction方法调用,最后是动态调用。

能够决定是否要让callAsFunction实现接受参数以及返回类型是什么的能力使其成为一个非常强大的功能。您确实可以根据自己的需求自定义此功能,并且由于可以向对象添加多个callAsFunction重载,因此可以在多个上下文中将单个对象用作函数。

0 人点赞