如何在Swift中正确输入链式函数生成器函数?

How to correctly type a chained functions generator function in Swift?

提问人:J0sh 提问时间:11/5/2023 最后编辑:J0sh 更新时间:11/6/2023 访问量:46

问:

特别是在 Swift 中(但老实说,我想要任何类型语言的良好解决方案),我想正确键入函数组合器函数

我确实对类型化语言和泛型有相当多的经验,但对 Swift 非常陌生。由于我在 Swift 中遇到了这个问题,所以这是我的主要关注点,但我想我不确定如何在任何类型的函数中做到这一点,而不简单地将所有内容声明为 .Any

(教科书)问题是这样的:

  1. 编写一个函数,该函数采用具有特定签名的函数数组并将它们组合在一起,以便一个函数的输出是下一个函数的输入。
  2. 使生成的函数的类型为,其中是数组中第一个函数的输入类型,是最后一个函数的输出类型。(I) -> OIO
  3. 让介于两者之间的每个函数(以某种方式)属于(当然)是之前函数的输出类型的类型。(PreviousOut) -> MyOutPreviousOut

这是我目前得到的:

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)))}
}

有什么想法吗?

Swift 泛型 类型 :函数式编程

评论


答:

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 所说,现在不可能编写这样的函数。也就是说,如果您只想以简洁的方式组合函数(没有任何冗长的内容),则可以使用自己的二进制运算符来组合函数。combinecompose(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

您还可以编写一个 ,实现为标识函数 - 可能更具可读性。combinecombine(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 无法做到的。