Swift是否存在任意闭包类型?如何实现多运算Operator协议
问题解答
Swift 中不存在可以代表任意闭包的内置类型。
Swift 是强静态类型语言,闭包的类型由参数数量、各参数类型、返回值类型共同决定,不同签名的闭包属于完全独立的类型,没有通用顶层类型可以覆盖所有闭包形态。即使将闭包强转为Any类型,也会丢失所有类型信息,调用时无法安全传参、获取返回值,极易触发运行时崩溃,完全不适合生产环境使用。
该编译错误本质是初始协议设计的局限性:最初的Operator协议将operate硬编码为单参数、单返回值的闭包签名,天然无法适配需要两个入参的中缀运算符,不需要强行寻找通用闭包类型,调整抽象逻辑即可解决问题。
可行实现方案
方案1:统一运算符执行接口(最适配表达式求值场景)
将所有运算符的执行入口统一为「接收操作数数组、返回运算结果」的形式,通过操作数数组的长度区分前缀、中缀、后缀运算符的参数要求,同时保留类型安全:
// 运算符相关错误定义 enum OperatorError: Error { case invalidOperandCount } // 通用运算符协议 protocol Operator { associatedtype Operand: Numeric var id: String { get } func operate(operands: [Operand]) throws -> Operand } // 前缀运算符实现(单操作数) struct PrefixOperator<T: Numeric>: Operator { typealias Operand = T var id: String private let actualLogic: (T) -> T init(id: String, operate: @escaping (T) -> T) { self.id = id self.actualLogic = operate } func operate(operands: [T]) throws -> T { guard operands.count == 1 else { throw OperatorError.invalidOperandCount } return actualLogic(operands[0]) } } // 中缀运算符实现(双操作数) struct InfixOperator<T: Numeric>: Operator { typealias Operand = T var id: String private let actualLogic: (T, T) -> T init(id: String, operate: @escaping (T, T) -> T) { self.id = id self.actualLogic = operate } func operate(operands: [T]) throws -> T { guard operands.count == 2 else { throw OperatorError.invalidOperandCount } return actualLogic(operands[0], operands[1]) } }
使用示例:
// 定义减法中缀运算符 let minus = InfixOperator<Int>(id: "-") { $0 - $1 } print(try? minus.operate(operands: [5, 3])) // 输出2 // 定义前缀自增运算符 let preIncrement = PrefixOperator<Int>(id: "++") { $0 + 1 } print(try? preIncrement.operate(operands: [3])) // 输出4
这个方案和表达式解析的栈式求值逻辑适配度很高:解析到对应运算符时,只需要从操作数栈弹出对应数量的操作数传入即可,内置的参数个数校验也能避免解析逻辑出错。
方案2:按运算符元数拆分协议
如果不想用数组传递操作数,也可以将不同参数数量的运算符拆分为独立协议,在解析阶段通过元数属性做分发:
// 基础公共协议 protocol Operator { var id: String { get } var arity: Int { get } // 1代表前缀/后缀单目运算符,2代表中缀双目运算符 } // 单目运算符协议 protocol UnaryOperator: Operator { associatedtype T func operate(_ item: T) -> T } // 双目运算符协议 protocol BinaryOperator: Operator { associatedtype T func operate(left: T, right: T) -> T }
这个方案的接口定义更直观,不需要做数组参数的合法性校验,但求值时需要额外判断运算符类型,再调用对应签名的运算方法。
内容的提问来源于stack exchange,提问作者Coding-is-Fun
相关产品推荐
相关产品推荐

