Defaulting arguments of existing functions in extensions

Hello! I'd like to preface by saying that I don't think I'd be able to provide an implementation, so the idea might not go anywhere, but I wanted to see if that's a problem that anyone else has.

I'm working with a protocol that we're going to call Frobulator:

protocol Frobulator<T> {
	associatedtype T
	associatedtype U
}

struct Foo<F: Frobulator> {
	init(frobulator: F, t: F.T, u: F.U) {
		// ...
	}
}

All good so far. But say I also have a protocol that lets me pick a default value for my arguments:

protocol DefaultValued {
	static var defaultValue: Self { get }
}

extension Foo where F.T: DefaultValued {
	init(frobulator: F, u: F.U) {
		self.init(frobulator: frobulator, t: .defaultValue, u: u)
	}
}

The problem becomes obvious here. I have one overload of init that doesn't have a T parameter because it's inferred from T: DefaultValued. I might want to do the same thing with U:

extension Foo where F.U: DefaultValued {
	init(frobulator: F, t: F.T) {
		self.init(frobulator: frobulator, t: t, u: .defaultValue)
	}
}

But then both T and U could have it, so I need another extension:

extension Foo where F.T: DefaultValued, F.U: DefaultValued {
	init(frobulator: F) {
		self.init(frobulator: frobulator, t: .defaultValue, u: .defaultValue)
	}
}

The growth is exponential. At 2 parameters with possibly-default values, I have 4 overloads. With another parameter I would have 8.

This is frequent in my code, and I think that it would be even more frequent if it was better supported by the language. As a real-world example (for just one default-able parameter, mind you):

public extension Collection where Index: Strideable, Index.Stride: BinaryInteger {
    func lowerBound(of item: Element, lessFunc: (T, T) -> Bool) -> Index {
        /* binary search implementation goes here */
    }
}

public extension Collection where Element: Comparable, Index: Strideable, Index.Stride: BinaryInteger {
    func lowerBound(of item: Element) -> Index {
        lowerBound(of: item, lessFunc: <)
    }
}

So I'm wondering if there's a way we could have extensions that only specify default values for arguments in other, existing overloads. Something like this:

public extension Collection where Element: Comparable, Index: Strideable, Index.Stride: BinaryInteger {
    func lowerBound(of item: Element, lessFunc: (T, T) -> Bool = (<)) -> Index
}

The declaration not having a body would signal that instead of adding an entirely new function, this adds a default value to a parameter of an existing overload. Swift would let you call lowerBound using the default lessFunc from this extension if not specified. The benefit of this syntax is that now, instead of having to add an exponential number of overloads, I can add a linear number of overloads and still get all the combinations I care for:

extension Foo where F.T: DefaultValued {
	init(frobulator: F, t: T = .defaultValue, u: U)
}
extension Foo where F.U: DefaultValued {
	init(frobulator: F, t: T, u: U = .defaultValue)
}

If both F.T and F.U are DefaultValued, I can call init(frobulator: ...) and the default values are selected from these.

There are possible overload ambiguities with this, but it seems to me that these are not new ambiguities, just the same old extension ambiguities.

Is this something that would be useful to anyone else? I vaguely recall a similar discussion before but I don't remember where it landed.

3 Likes