[Wish] Preferred generic types

Generics functions are great at reducing code duplication, but there's a silly caveat that you must overload such functions to disambiguate literals (like 123 or "abc"). Additionally, the number of overloads scales poorly with the number of generic arguments. Here's an example of this disambiguation strategy:

Given the following context.
protocol Abstraction: ExpressibleByIntegerLiteral { }

struct Foo: Abstraction { ... }

struct Bar: Abstraction { ... }
func silly(_ first: Foo, _ second: Foo) {
    print(first, second)
}

func silly(_ first: Foo, _ second: some Abstraction) {
    print(first, second)
}

func silly(_ first: some Abstraction, _ second: Foo) {
    print(first, second)
}

func silly(_ first: some Abstraction, _ second: some Abstraction) {
    print(first, second)
}

The maintenance burden isn't terrible when you only need to disambiguate one generic type. Still, even that case is a testing headache since generic tests won't take the more specific overloads just because their types match. I think it would be great if it were possible to annotate generic type parameters with a preferred (or favored) type. Imagine something like this instead:

func silly(
    _  first: @favoring(Foo) some Abstraction, 
    _ second: @favoring(Foo) some Abstraction
) {
    print(first, second)
}

This hypothetical syntax would be equivalent to the following procedure: "Try interpreting first as an instance of Foo if first doesn't match some Abstraction then repeat this process with the second argument."

1 Like

It reminds me of C++ template specialization!

I haven’t had to do this a lot but then again mostly because I build around this issue and tend to use enums as a wrapper type, this would help get rid of some maintenance cost in my code while improving usability.

I’m not sure about the proposed syntax but I can’t think of another suggestion.

Specialisation can be achieved through the inlineable annotation(s).

To be clear, I'm only asking for a way to disambiguate literals without adding new code paths.

I think what you’re looking for is some ability to state default generic parameters:

func foo<T: ExpressibleByIntegerLiteral = Foo>(_: T) {}
foo(123)

You can simulate this today with a default metatype parameter:

func foo<T: ExpressibleByIntegerLiteral>(_: T, _: T.Type = Foo.self) {}
foo(123)
5 Likes

Something to that effect. <T: Abstracton = Foo> seems intuitive. I'd be happy with it.

The problem with the workaround (as I understand it) is that I'd have to pass the metatype in the non-Foo case and, therefore, end up wanting/needing another overload. Also, it gives me this error :(

// 🛑 Cannot use default expression for inference of 'T.Type'
// 🛑 because it is inferrable from parameters #0, #1

struct Foo: ExpressibleByIntegerLiteral { init(integerLiteral:  Int) { } }
func foo<T: ExpressibleByIntegerLiteral>(_: T, _: T.Type = Foo.self) { }
foo(123)
2 Likes