提问人:J0sh 提问时间:11/5/2023 最后编辑:J0sh 更新时间:11/6/2023 访问量:46
如何在Swift中正确输入链式函数生成器函数?
How to correctly type a chained functions generator function in Swift?
问:
特别是在 Swift 中(但老实说,我想要任何类型语言的良好解决方案),我想正确键入函数组合器函数。
我确实对类型化语言和泛型有相当多的经验,但对 Swift 非常陌生。由于我在 Swift 中遇到了这个问题,所以这是我的主要关注点,但我想我不确定如何在任何类型的函数中做到这一点,而不简单地将所有内容声明为 .Any
(教科书)问题是这样的:
- 编写一个函数,该函数采用具有特定签名的函数数组并将它们组合在一起,以便一个函数的输出是下一个函数的输入。
- 使生成的函数的类型为,其中是数组中第一个函数的输入类型,是最后一个函数的输出类型。
(I) -> O
I
O
- 让介于两者之间的每个函数(以某种方式)属于(当然)是之前函数的输出类型的类型。
(PreviousOut) -> MyOut
PreviousOut
这是我目前得到的:
func combine<I, O>(_ first: (I) -> Any, _ rest: ((Any) -> Any)...) -> (I) -> O {
return { input in
let firstResult = first(input)
return rest.reduce(firstResult) {$1($0)} as! O
}
}
...这适用于该类型,但当然“无法推断通用参数 O
”,即使,我们仍然在组合函数调用之间没有任何类型的类型保存。I
--更新-- 这是一个固定的示例,其中恰好有 3 个处理器函数,确实有效。虽然这显然不是我想要实现的目标,但它可能更好地说明了需要什么:
func combine<I, O, A, B>(
_ first:@escaping (I) -> A,
_ second:@escaping (A) -> B,
_ third:@escaping (B) -> O
) -> (I) -> O {
return {input in third(second(first(input)))}
}
有什么想法吗?
答:
0赞
Cristik
11/5/2023
#1
不幸的是,即使考虑到新引入的类型参数包,您也有点不走运。
ATM,唯一的解决方案是编写函数的重载版本,这需要设置要支持的功能数量的上限。combine
// zero functions
func combine() {
}
// one function
func combine<I, O>(_ f: @escaping (I) -> O) -> (I) -> O {
f
}
// two functions
func combine<I, O, T>(
_ f1: @escaping (I) -> T,
_ f2: @escaping (T) -> O
) -> (I) -> O {
{ f2(f1($0)) }
}
// three functions
func combine<I, O, T1, T2>(
_ f1: @escaping (I) -> T1,
_ f2: @escaping (T1) -> T2,
_ f3: @escaping (T2) -> O
) -> (I) -> O {
{ f3(f2(f1($0))) }
}
// four functions
func combine<I, O, T1, T2, T3>(
_ f1: @escaping (I) -> T1,
_ f2: @escaping (T1) -> T2,
_ f3: @escaping (T2) -> T3,
_ f4: @escaping (T3) -> O
) -> (I) -> O {
{ f4(f3(f2(f1($0)))) }
}
// and so on...
基本上,您必须在代码中手动编写任意数量的重载。
0赞
Sweeper
11/6/2023
#2
正如 Cristik 所说,现在不可能编写这样的函数。也就是说,如果您只想以简洁的方式组合函数(没有任何冗长的内容),则可以使用自己的二进制运算符来组合函数。combine
compose(compose(f, g), h)
precedencegroup FunctionComposition {
associativity: left
}
infix operator ~: FunctionComposition
func ~<T, U, V>(f: @escaping (T) -> U, g: @escaping (U) -> V) -> (T) -> V {
{ g(f($0)) }
}
然后,您可以为任意数量的函数编写等等。f ~ g ~ h
您还可以编写一个 ,实现为标识函数 - 可能更具可读性。combine
combine(f ~ g ~ h)
如果要强制人们使用 ,可以将函数临时包装成一个结构体,然后可以解包它:combine
~
combine
struct Function<T, U> {
// fileprivate so callers cannot access this directly and must use "combine"
fileprivate let apply: (T) -> U
}
func ~<T, U, V>(f: @escaping (T) -> U, g: @escaping (U) -> V) -> Function<T, V> {
Function { g(f($0)) }
}
func ~<T, U, V>(f: Function<T, U>, g: @escaping (U) -> V) -> Function<T, V> {
Function { g(f.apply($0)) }
}
func combine<T, U>(_ f: Function<T, U>) -> (T) -> U { f.apply }
但我个人认为这有点矫枉过正。
评论
0赞
J0sh
11/9/2023
哇,好吧,这是对 Swift 的深入研究。😅 所以现在我需要去学习二进制运算符和......你认为像新宏这样的东西也可能是一个可能的解决方案吗?🤔infix
0赞
Sweeper
11/9/2023
@J0sh宏在这里无济于事,因为在将宏的所有参数传递给宏实现之前,必须先进行类型检查,但类型检查正是 Swift 无法做到的。
评论