[swift-evolution] Proposal: Allow explicit type parameter specification in generic function call

Dave Abrahams dabrahams at apple.com
Thu Dec 1 16:09:18 CST 2016


on Wed Nov 30 2016, Douglas Gregor <swift-evolution at swift.org> wrote:

>> On Nov 30, 2016, at 4:09 PM, Dave Abrahams via swift-evolution
> <swift-evolution at swift.org> wrote:
>> 
>> 
>> on Mon Nov 28 2016, Douglas Gregor
>> <swift-evolution at swift.org
>
>> <mailto:swift-evolution at swift.org>>
>> wrote:
>> 
>>>> On Nov 21, 2016, at 3:05 PM, Ramiro Feria Purón via swift-evolution
>>> <swift-evolution at swift.org> wrote:
>>>> 
>>>> Problem:
>>>> 
>>>> Currently, it is not possible to be explicit about the generic parameters (type parameters) in a
>>> generic function call. Type parameters are inferred from actual parameters:
>>> 
>>>> 
>>>> func f<T>(_ t: T) {
>>>> 
>>>>    //..
>>>> }
>>>> 
>>>> f(5)            // T inferred to be Int
>>>> f("xzcvzxcvx")  // T inferred to be string 
>>>> 
>>>> If no type parameter is involved in the formal parameters, the type parameter needs to be used somehow as part of the return type. For example:
>>>> 
>>>> func g<T>(_ x: Int) -> [T] {
>>>> 
>>>>    var result: [T] = []
>>>> 
>>>>    //..
>>>> 
>>>>    return result
>>>> }
>>>> 
>>>> In such cases, the type parameters must be inferrable from the context:
>>>> 
>>>> g(7)                            // Error: T cannot be inferred
>>>> let array = g(7)                // Error: T cannot be inferred
>>>> let array: [String] = g(7)      // Ok: T inferred to be String
>>>> let array = g<String>(7)        // Error: Cannot explicitly specialise generic function
>>>> 
>>>> 
>>>> 
>>>> Proposed Solution:
>>>> 
>>>> Allow explicit type parameters in generic function call:
>>>> 
>>>> let _ = g<String>(7)            // Ok
>>>> 
>>>> 
>>>> 
>>>> Motivation:
>>>> 
>>>> Consider the following contrived example:
>>>> 
>>>> class Vehicle {
>>>>    var currentSpeed = 0
>>>>    //..
>>>> }
>>>> 
>>>> class Bicycle: Vehicle {
>>>>    //..
>>>> }
>>>> 
>>>> class Car: Vehicle {
>>>>    //..
>>>> }
>>>> 
>>>> @discardableResult
>>>> func processAll<T: Vehicle>(in vehicles: [Vehicle], condition: (Vehicle) -> Bool) -> [T] {
>>>> 
>>>>    var processed: [T] = []
>>>> 
>>>>    for vehicle in vehicles {
>>>>        guard let t = vehicle as? T, condition(vehicle) else { continue }
>>>>        //..
>>>>        processed.append(t)
>>>>    }
>>>> 
>>>>    return processed
>>>> }
>>>> 
>>>> func aboveSpeedLimit(vehicle: Vehicle) -> Bool {
>>>>    return vehicle.currentSpeed >= 100
>>>> }
>>>> 
>>>> 
>>>> let processedVehicles = processAll(in: vehicles, condition: aboveSpeedLimit) // Uh, T inferred to
>>> be Vehicle!
>>>> let processedCars: [Car] = processAll(in: vehicles, condition: aboveSpeedLimit) // T inferred to
>>> be Car
>>>> processAll<Bicycle>(in: vehicles, condition: aboveSpeedLimit) // This should be allowed under this
>>> proposal
>>>> 
>>>> 
>>>> Notes:
>>>> 
>>>> If necessary, the (real life) Swift code that lead to the proposal could be shared.
>>> 
>>> This seems completely reasonable to me. I had always expected us to
>>> implement this feature, but we never got around to it, and it wasn’t a
>>> high priority because one can always use type inference. Additionally,
>>> there were a few places where we originally thought we wanted this
>>> feature, but prefer the more-explicit form where the user is required
>>> to explicitly pass along a metatype. unsafeBitCast is one such case:
>>> 
>>> 	func unsafeBitCast<T, U>(_ x: T, to: U.Type) -> U
>>> 
>>> Even if we had the ability to provide explicit type arguments, we
>>> would *not* want to change this signature to
>>> 
>>> 	func unsafeBitCast<U, T>(_ x: T) -> U     // bad idea
>>> 
>>> because while it makes the correct usage slightly cleaner:
>>> 
>>> 	unsafeBitCast<Int>(something)	// slightly prettier, but...
>>> 
>>> it would enable type inference to go wild with unsafe casts:
>>> 
>>> 	foo(unsafeBitCast(something))	// just cast it to.. whatever	
>>> 
>>> which is… not great.
>> 
>> Yeah, but IMO ideally we'd have a way to inhibit deduction of some
>> generic type parameters.
>
> Well, I’d say we already have it: it’s the pass-a-metatype approach
> already used by unsafeBitCast, and I think usages of that API read
> really, really well as it is.

IMO they are marred by “.self,” but that's really a minor issue. More
importantly, they suggest that the metatype argument will be used in
some dynamic way (e.g. by calling a static method or an init), instead
of just as a way to get the right type inference.  In some cases that
can make a dramatic difference in the resulting semantics.

    func polymorphicSomething<T>(_: T.Type) {
      ...
    }
    
    class Base {}
    class Derived : Base {}

    func otherThing(x: Base) {
      // Surprise! I'm going to ignore the dynamic type you gave me and
      // just use Base
      polymorphicSomething(type(of: y)) 
    }

    otherThing(Derived())

This is exactly why we have

     MemoryLayout<T>.size

and not

     memoryLayout(Foo.self).size

>>  I might even be willing to inhibit deduction,
>> by default, of all generic function type parameters that don't appear in
>> the parameter list.
>
> I’m not a fan of this for the reasons I and Ramiro outlined.

Details please?  AFAICT Ramiro outlined arguments *for* this approach and
you merely referred to your thread with me in explaining why you don't
like it.

-- 
-Dave



More information about the swift-evolution mailing list