Generic Function Specialization

I have been looking for a project that will really allow me to dive into the compiler and implement a new feature of some sort. Recently, I was writing a library to provide additional functionality to Combine called QuickCombine. During this process, I had the desire explicitly specialize the type of one of my generic functions, only to find out that this feature was missing from Swift.

After doing some research, I found that this had been discussed before on multiple occassions:

Right now, one cannot explicitly specialize a generic function:

func foo<T>(_ value: T) { ... }

let specializedFoo = foo<Int>  // Error: Cannot explicitly specialize a generic function

With generic function specialization, the code above would be valid and specializedFoo would be of type (Int) -> Void.

In Swift, we are currently allowed to specialize the generic type of an enum, struct, and class; it seems only logical to extend this behavior to functions as well. In fact, the example above parallels the use of a typealias when specializing a specific type (i.e. typealias IntArray = Array<Int>).

As well, providing functions with the ability to explicitly specify their generic type parameters would allow us to lift the restriction wherein a generic type parameter must be used in the function signature. Currently, this is not allowed:

func typeOf<T>() -> String {  // Error: Generic parameter 'T' is not used in function signature
    return "\(T.self)"
}

Lifting this restriction would make functions like typeOf valid, allowing it to be used in the following fashion:

print(typeOf<Int>())
// Prints "Int"

Implementation

To start off, I would like to try to implement this feature for just regular functions and initializers, ignoring subscripts and closures for now.

I have a few questions regarding the implementation of this feature:

  • Is there any fundamental barrier that has prevented this from being added to Swift thus far or has no one tackled this issue yet?
  • Where should I start when implementing this?
  • What areas of the compiler would be affected by this?
  • Any problems that a feature of this sort would introduce.
  • Anything else important I should know regarding this feature.
  • Any other advice for me when going about implementing this.

Before formally proposing or starting a discussion on a feature of this sort, I wanted to try and actually implement some of it first... so here I am :wink:.

All help is appreciated. Thank you!

I'm also a newbie to the compiler stuff, so more experienced people may correct me, but there is one thing that stands out right away:

foo in itself is not a type, it's a function, and you only can specialize types in Swift. What you can do is the following:

func foo<T>(arg: T) { }

let bar: (Int) -> Void = foo(arg:)

I'm not sure if this is better than type(of: Int.self) (or what's the problem exactly)?

I initially found the lack of this feature to be a problem when working on a function called tryFutureMap in my QuickCombine library. This function basically allows you to perform an asynchronous, one to one, mapping operation that may produce an error. Here is the definition of one of the overloads of the function:

public extension Publisher where Failure == Never {
    func tryFutureMap<T, U>(_ transform: @escaping (_ value: Output, _ promise: @escaping (Result<T, U>) -> Void) -> Void) -> Publishers.TryFutureMap<Publishers.SetFailureType<Self, U>, T> where U: Error {
        return Publishers.TryFutureMap(upstream: self.setFailureType(to: U.self), transform: transform)
    }
}

It is standard in a call to tryFutureMap to make at least two calls to the promise argument of the transform closure, one for a successful result and one for a failure, although there are many scenarios where one might want to call promise more than two times. The problem here is that, for its intended use case, it is not practical to provide tryFutureMap with a single-expression closure, and as such, Swift requires me to explicitly state the type for promise, which is always a fairly long type signature that is easy to mess up.

Just("Hello")
    .tryFutureMap { (value, promise: @escaping (Result<Int, CustomError>) -> Void) in
        if value == "Hello" {
            promise(.success(1))
        } else {
            promise(.failure(CustomError()))
        }
    }

With the ability to specialize the type of a generic function at its call site, the above usage of tryFutureMap could be replaced with the following:

Just("Hello")
    .tryFutureMap<Int, CustomError> { value, promise in
        if value == "Hello" {
            promise(.success(1))
        } else {
            promise(.failure(CustomError()))
        }
    }

In this case, the user should not have to tell Swift the type of the promise argument as Swift knows it already with exception for the specialized types for T and U. Having to state the additional information seems error prone and congests the call-site. Generic function specialization would remove this redundancy.

FWIW, there was some discussion of this a few years back that might be helpful: Proposal: Allow explicit type parameter specification in generic function call

Thanks @owenv. I just updated my post with the expanded names of previous discussions on this topic and their associated links.

Also, while we agree that type names are capitalized while function names are not, imagine this situation:

struct foo<T> {
    let t: T? = nil
}
            
func foo<T>() { /* something with T */ }
            
let bar = foo<Int>()

What should the compiler choose: the function invocation (aka bar will be an instance of Void) or the constructor (initializer) of the struct? Currently, it would be the latter. How would you disambiguate though?

I don't think you would be able to declare a function called foo after declaring the type foo in the first place. Currently, the following example produces an error:

struct foo<T> {
    let t: T? = nil
    init() { }
}

func foo() { /* ... */ }  // Error: Invalid redeclaration of 'foo()'

I may be wrong but I'm pretty sure Swift catches these types of invalid redeclarations in the first place. I don't see how adding the ability to explicitly specify the type of generic functions would create any new ambiguities that did not previously exist.

1 Like

Moreover, there aren't really any big differences between an unspecialized struct and an unspecialized function. Consider the following example:

struct foo<T> {
    init(_: T) { }
}

func bar<T>(_: T) { }

foo<Set>([2])
bar([2])

The only notable difference is that with bar, you cannot tell the compiler that T is a Set and not an Array. To use a literal Set, you currently have to explicitly cast [2] to a Set or assign it to a variable whose type is specified and pass that into bar. Either way, this seems excessive and verbose.

The need for generic function specialization really becomes apparent though when using a closure whose body is type-checked separately for the expression itself and when a generic argument only takes up part of the type signature like in my tryFutureMap example above. Consider the more following example:

struct foo<T> {
    init(_: @escaping (@escaping (T, Float, Double, String) -> Void) -> Void) { }
}

func bar<T>(_: @escaping (@escaping (T, Float, Double, String) -> Void) -> Void) { }

foo<Int> { baz in
    baz(1, 2.0, 3.0, "")
    baz(4, 5.0, 6.0, "")
}

bar { (baz: @escaping (Int, Float, Double, String) -> Void) in
    baz(1, 2.0, 3.0, "")
    baz(4, 5.0, 6.0, "")
}

Specializing foo with Int as its generic parameter gives the compiler the knowledge it needs to not require the type of baz be stated. With this in mind, it seems entirely unnecessary that the caller of bar should have to specify the entire type of baz, even though the compiler knows its type with exception of the single Int argument as T. When not specified, the compiler emits the error message "Generic parameter 'T' could not be inferred." It does not need all of baz, just T, though in Swift's current state you can't just provide the type of T. Allowing functions to explicitly specify their specialized generic parameters allows the line of bar's call-site to be reduced to bar<Int> { baz in, just like foo.

Providing functions with the ability to explicitly specialize their generic type parameters allows you to omit unnecessary information that the compiler already knows, thus removing redundancies and cleaning up the call-site.

Terms of Service

Privacy Policy

Cookie Policy