Pipe function types using variadic generics, possible to access "index" of a type in a pack?

This code does not scale, and I thought I can write it using variadic generics...

public protocol Function {
	associatedtype Input
	associatedtype Output
	func call(_ input: Input) -> Output
}

/// Returns `b(a)`, where `a` and `b` are types conforming to `Function`
public func pipe<A, B>(
	_ a: A,
	_ b: B
) -> ((A.Input) -> B.Output)
where
A: Function,
B: Function,
A.Output == B.Input
{
	{ (f: A.Input) -> B.Output in
		b.call(
			a.call(f)
		)
	}
}

/// Returns `c(b(a))`, where `a`, `b` and `c` are types conforming to `Function`
public func pipe<A, B, C>(
	_ a: A,
	_ b: B,
	_ c: C
) -> ((A.Input) -> C.Output)
where
A: Function,
B: Function,
C: Function,
A.Output == B.Input, B.Output == C.Input
{
	{ (f: A.Input) -> C.Output in
		c.call(
			b.call(
				a.call(f)
			)
		)
	}
}

While I've read most pitches about variadic generics, I have not tried using it it earlier.

So I quite immediately felt lost... I need to express the input of a type is the output of the previous type, but don't think that kind of syntax for type constraints is brought up in SE-0393

public func pipe<First: Function, each M: Function, Last: Function>(
	first: First,
	middle: repeat each M,
	last: Last
) -> ((F.Input) -> L.Output) where repeat (each M).Output == (each M+1).Input // Syntax?!?
{
	fatalError("Syntax?! Feels like I wanna use a macro to implement this... LOL.")
}

It feels like... like I wanna create a pack of pair of types...

Never mind how lost I am (quite lost), is my goal achievable with the variadic generics tool shipped in Swift 5.9?

1 Like

”func reduce” is maybe a better name. The function reduces multiple types conforming to Function into a new single one: (First.Input) -> Last.Output

It is certainly not possible.

However, in some hypothetical future, you could use this awful workaround:

public func pipe<First: Function, each AllOtherThanLast: Function, each Item: Function, each AllOtherThanFirst: Function, Last: Function>(_ functions: repeat each Item) -> (First.Input) -> (Last.Output) where (First, repeat each AllOtherThanFirst) == (repeat each Item), (repeat each AllOtherThanLast, Last) == (repeat each Item), (repeat each AllOtherThanLast.Output) == (repeat each AllOtherThanFirst.Input) {
  fatalError("Don't get me started on the implementation")
}

Didn't manage to find a solution with Variadic Generics at the moment but find a way to do it with Result Builder

protocol Function {
    associatedtype Input
    associatedtype Output
    func call(_ input: Input) -> Output
}

@resultBuilder
struct FunctionBuilder {
    static func buildPartialBlock<F: Function>(first: F) -> (F.Input) -> F.Output {
        first.call
    }
    static func buildPartialBlock<Input, Output, Next: Function>(
        accumulated: @escaping (Input) -> Output,
        next: Next
    ) -> (Input) -> Next.Output where Next.Input == Output {
        { (input: Input) in
            next.call(accumulated(input))
        }
    }
}

func reduce<Input, Output>(
    @FunctionBuilder _ builder: () -> (Input) -> Output
) -> (Input) -> Output {
    builder()
}

struct FuncRandom: Function {
    func call(_ value: UInt) -> Int {
        Int.random(in: 0..<Int(value))
    }
}
struct FuncToString: Function {
    func call(_ value: Int) -> String {
        "\(value)"
    }
}
struct FuncFromString: Function {
    func call(_ value: String) -> Int {
        Int(value) ?? 0
    }
}

let test = reduce {
    FuncRandom()
    FuncToString()
    FuncFromString()
}

test(25)
1 Like

Thx! Yes I realized it the other day when I talked to @GreatApe about it, and using ResultBuilder is what I am using it for!

Only that I wrote that ResultBuilder before the buildPartial was introduced :partying_face:

Note that if you want to keep all the types composing your pipeline you can with variadic generic and the result builder buildPartialBlock approach :slight_smile:

1 Like

I guess you might use a custom operator to achieve the same result

Haha that is the next step, no need for a superficial protocol, will try to pipe output if any arity to input of same pattern.

I used |> infix operator before