Proposal: Allow explicit type parameter specification in generic function call


(Ramiro Feria Purón) #1

*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.


(Dave Abrahams) #2

I think we want this capability, but it isn't a high priority and it's
an additive feature so it's certainly out-of-scope for Swift 4 phase 1.

···

on Mon Nov 21 2016, Ramiro Feria Purón <swift-evolution@swift.org> wrote:

*Proposed Solution:*

Allow explicit type parameters in generic function call:

let _ = g<String>(7) // Ok

--
-Dave


(Benjamin Spratling) #3

I don’t understand how your “contrived example" could be improved by this new feature instead of existing features.
It sounds like you’re trying to combine two entirely different things into one function. One is a filter, which is easily made with .flatMap

let vehicles:[Vehicle] = [Bicycle(), Car()]
let cars = vehicles.flatMap { (vehicle) -> Car? in
  return vehicle as? Car
} //[Car]
//cars is an Array<Car>

Since your processAll func would be restricted to Vehicle subclasses anyway, you won’t be calling any sub-class specific code on them without making explicit references to them, so do you need the type casting for anything other than the return?

If so, consider passing the exact class in as an argument:

func process(in vehicles:[Vehicle], type:Vehicle.Type) {
  for vehicle in vehicles {
    if type(of:car) != type) { continue }
    
So, I’m not convinced that your “contrived example" are best solved with a new language feature.

-Ben

···

On Nov 21, 2016, at 5:05 PM, Ramiro Feria Purón via swift-evolution <swift-evolution@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.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #4

I’m not sure what are you trying to solve here. Your code from the motivation compiles just fine in my Playground.

About the g function:

g(7) as [String]
let _: [String] = g(7)
Personally I don’t like to see <Type> noise on functions in Swift.

···

--
Adrian Zubarev
Sent with Airmail

Am 22. November 2016 um 00:06:02, Ramiro Feria Purón via swift-evolution (swift-evolution@swift.org) schrieb:

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.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Ramiro Feria Purón) #5

Inferring the type of an expression from its surrounding context is not
necessarily an ideal feature in a strongly typed programming language.

The goal here is to be able to be explicit about the type, in an elegant
and clear way, especially when there is no (need for a) context.

Note that the <Type> "noise" would only be necessary when the type could
not be inferred from the actual parameters (and if the current surrounding
context type inference was removed, which is not part of the proposal).

The line of code from the Motivation that does not compile is:

processAll<Bicycle>(in: vehicles, condition: aboveSpeedLimit)
        // This should be allowed under this proposal

···

2016-11-22 10:35 GMT+11:00 Adrian Zubarev <adrian.zubarev@devandartist.com>:

I’m not sure what are you trying to solve here. Your code from the
motivation compiles just fine in my Playground.

About the g function:

g(7) as [String]
let _: [String] = g(7)

Personally I don’t like to see <Type> noise on functions in Swift.

--
Adrian Zubarev
Sent with Airmail

Am 22. November 2016 um 00:06:02, Ramiro Feria Purón via swift-evolution (
swift-evolution@swift.org) schrieb:

*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.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Ramiro Feria Purón) #6

Thanks Dave!

As another example, consider it as part of this common pattern:

class A<T> {

    init() {

        // ..

    }

}

class B<T>: A<T> {

    override init() {

        // ..

    }

}

class Factory {

    class func makeA<T>() -> A<T> { return B<T>() }

    //..

}

Factory.makeA<Int>()

Kind regards,

Ramiro

···

2016-11-22 14:14 GMT+11:00 Dave Abrahams via swift-evolution < swift-evolution@swift.org>:

on Mon Nov 21 2016, Ramiro Feria Purón <swift-evolution@swift.org> wrote:

> *Proposed Solution:*
>
> Allow explicit type parameters in generic function call:
>
> let _ = g<String>(7) // Ok

I think we want this capability, but it isn't a high priority and it's
an additive feature so it's certainly out-of-scope for Swift 4 phase 1.

--
-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Douglas Gregor) #7

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.

I’d like one bit of clarification in the proposal. Right now, one is not permitted to have a type parameter in a generic function that isn’t used somewhere in its signature, e.g.,

  func f<T>() -> Void { … } // error: T is not part of the signature of f()

This restriction is obvious in today’s Swift, because there is absolutely no way one could ever use this function. With your proposed extension, it would be possible to use this function. Does the restriction remain or is it lifted?

Personally, I’d like the restriction to stay, because it feels like such functions fall into the same camp as unsafeBitCast: if the type parameter affects how the function operates but is *not* part of its signature, then it should be expressed like a normal parameter (of a metatype). It also helps provide better diagnostics when changing a generic function to no longer require one of its type parameters.

And, as Dave notes, it’s effectively syntactic sugar, so it belongs in Swift 4 stage 2.

  - Doug

···

On Nov 21, 2016, at 3:05 PM, Ramiro Feria Purón via swift-evolution <swift-evolution@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.


(Anton Zhilin) #8

I disagree with the suggestion. Unlike C++'s templates, a Swift's generic
function is semantically a single function. One can say that together with
a metatype parameter, we pass in witness table.

What I think we should do is make metatypes easier to use. *Adrian and I
have a PR* that has already been waiting for almost a month:
https://github.com/apple/swift-evolution/pull/553
Plus, removal of .self could simplify such calls.


(Sean Heber) #9

Doesn't this work?

processAll(in: vehicles, condition: aboveSpeedLimit) as [Bicycle]

l8r
Sean

···

Sent from my iPad

On Nov 21, 2016, at 6:11 PM, Ramiro Feria Purón via swift-evolution <swift-evolution@swift.org> wrote:

Inferring the type of an expression from its surrounding context is not necessarily an ideal feature in a strongly typed programming language.

The goal here is to be able to be explicit about the type, in an elegant and clear way, especially when there is no (need for a) context.

Note that the <Type> "noise" would only be necessary when the type could not be inferred from the actual parameters (and if the current surrounding context type inference was removed, which is not part of the proposal).

The line of code from the Motivation that does not compile is:

processAll<Bicycle>(in: vehicles, condition: aboveSpeedLimit) // This should be allowed under this proposal

2016-11-22 10:35 GMT+11:00 Adrian Zubarev <adrian.zubarev@devandartist.com>:

I’m not sure what are you trying to solve here. Your code from the motivation compiles just fine in my Playground.

About the g function:

g(7) as [String]
let _: [String] = g(7)
Personally I don’t like to see <Type> noise on functions in Swift.

--
Adrian Zubarev
Sent with Airmail

Am 22. November 2016 um 00:06:02, Ramiro Feria Purón via swift-evolution (swift-evolution@swift.org) schrieb:

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.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Haravikk) #10

You can still do this using one of the following:

  class Factory {
    class func makeA<T>(_ theType:T.Type) -> A<T> { return B<T>() }
  }
  let foo = Factory.makeA(Int.self)

  class Factory<T> {
    class func makeA() -> A<T> { return B<T>() }
  }
  let foo = Factor<Int>.makeA()

i.e- we already have the tools to do this; in the form of inference, passing the type, and adding the generic to the type itself. I guess I'm just not seeing a clear advantage that the proposed syntax adds except to allowing dropping of .self on the pass-the-type form of call.

···

On 22 Nov 2016, at 04:18, Ramiro Feria Purón via swift-evolution <swift-evolution@swift.org> wrote:

Thanks Dave!

As another example, consider it as part of this common pattern:

class A<T> {
    init() {
        // ..
    }
}

class B<T>: A<T> {
    override init() {
        // ..
    }
}

class Factory {
    
    class func makeA<T>() -> A<T> { return B<T>() }
    //..
}

Factory.makeA<Int>()


(Ramiro Feria Purón) #11

Yes, it does; as it does let processedCars: [Car] = processAll(in: vehicles,
condition: aboveSpeedLimit)

The core of the matter is that it is nowhere stated in the call
(expression) processAll(in: vehicles, condition: aboveSpeedLimit) that
the specialised version processAll<Bicycle> is the one to be called.

Although we are aware of the semantics, it does not read naturally and is
somehow confusing. The ultimate goal is to make swift cleaner and clear. It
rather feels like invoking non-specialised, non-generic function, and then
casting the result to an array of a given type, which is not the case.

All the best,
Ramiro

···

2016-11-22 12:09 GMT+11:00 Sean Heber <sean@fifthace.com>:

Doesn't this work?

processAll(in: vehicles, condition: aboveSpeedLimit) as [Bicycle]

l8r
Sean

Sent from my iPad

On Nov 21, 2016, at 6:11 PM, Ramiro Feria Purón via swift-evolution < > swift-evolution@swift.org> wrote:

Inferring the type of an expression from its surrounding context is not
necessarily an ideal feature in a strongly typed programming language.

The goal here is to be able to be explicit about the type, in an elegant
and clear way, especially when there is no (need for a) context.

Note that the <Type> "noise" would only be necessary when the type could
not be inferred from the actual parameters (and if the current surrounding
context type inference was removed, which is not part of the proposal).

The line of code from the Motivation that does not compile is:

processAll<Bicycle>(in: vehicles, condition: aboveSpeedLimit)
          // This should be allowed under this proposal

2016-11-22 10:35 GMT+11:00 Adrian Zubarev <adrian.zubarev@devandartist.com
>:

I’m not sure what are you trying to solve here. Your code from the
motivation compiles just fine in my Playground.

About the g function:

g(7) as [String]
let _: [String] = g(7)

Personally I don’t like to see <Type> noise on functions in Swift.

--
Adrian Zubarev
Sent with Airmail

Am 22. November 2016 um 00:06:02, Ramiro Feria Purón via swift-evolution (
swift-evolution@swift.org) schrieb:

*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.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Andrew Trick) #12

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…

Angle brackets in function calls are hideous. This is objectively more clear and much prettier IMO:

  unsafeBitCast(something, to: Int)

it would enable type inference to go wild with unsafe casts:

  foo(unsafeBitCast(something)) // just cast it to.. whatever

which is… not great.

I’d like one bit of clarification in the proposal. Right now, one is not permitted to have a type parameter in a generic function that isn’t used somewhere in its signature, e.g.,

  func f<T>() -> Void { … } // error: T is not part of the signature of f()

This restriction is obvious in today’s Swift, because there is absolutely no way one could ever use this function. With your proposed extension, it would be possible to use this function. Does the restriction remain or is it lifted?

Personally, I’d like the restriction to stay, because it feels like such functions fall into the same camp as unsafeBitCast: if the type parameter affects how the function operates but is *not* part of its signature, then it should be expressed like a normal parameter (of a metatype). It also helps provide better diagnostics when changing a generic function to no longer require one of its type parameters.

+1 for required type parameters being normal parameters.

I think the case mentioned in the proposal reads much better as:

  processAll(in: vehicles, as: Bicycle, condition: aboveSpeedLimit)

If angle brackets can be limited to generic definitions and type names, that’s a great accomplishment.

-Andy

···

On Nov 28, 2016, at 10:11 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 21, 2016, at 3:05 PM, Ramiro Feria Purón via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

And, as Dave notes, it’s effectively syntactic sugar, so it belongs in Swift 4 stage 2.

  - Doug

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Ramiro Feria Purón) #13

Douglas,

Regarding the question on the restriction for type parameters to appear on
the signature, the answer is remain. The proposal does not intend this
restriction to be lifted.

One might expect to find a few legitimate cases where having it lifted
would be handy or desirable. They seem to emerge often, for example,
while developing components using Core Data. From a real case scenario:

protocol Activable { var dateLastActive: Date { get } }

class A: NSManagedObject {}

class B: NSManagedObject {}

//..

class Z: NSManagedObject {}

extension A: Activable { var dateLastActive: Date { return Date() } }

extension B: Activable { var dateLastActive: Date { return Date() } }

//..

extension Z: Activable { var dateLastActive: Date { return Date() } }

func deleteInactiveObjects<T: NSManagedObject>(since date: Date, inContext
context: NSManagedObjectContext) where T: Activable {

    //..

}

// for the sake of the example

let context = NSManagedObjectContext(concurrencyType: .
privateQueueConcurrencyType)

let yesterday = Date()

let oneWeekAgo = Date()

deleteInactiveObjects<A>(since: yesterday, inContext: context)

deleteInactiveObjects<B>(since: oneWeekAgo, inContext: context)

//..

(here again, as you mention, the parameter affects how the function
operates, yet it is not part of the signature)

If the restriction was lifted, however, it would also be in detriment of
the educational value of the proposal (apart from your arguments above). When
defining a generic function, it feels natural to expect all of the type
parameters to be present in the generic function signature. Relaxing this
rule could be perceived by the novice as an invitation to an obscure
design. In the best case, it would generate doubts about its actual intent.

From a pedagogical perspective, the proposal aims to save Swift from

disappointment when this topic is brought to discussion, say whether at the
end of a Programming or a Compilers undergraduate course -- albeit fully
understanding (or implementing) the current alternatives could be an
excellent exercise for the class.

From the (primary) language and development perspective, I don't think it

could be expressed in a better way than in your lines, which are truly
appreciated:

"*That’s how I see this proposal: not as a replacement for the metatype
parameter idiom that unsafeBitCast uses, but as a way to be more explicit
at particular call sites when type inference either fails (e.g., due to
lack of contextual type information), produces a result different than what
is desired, or is sufficiently complicated that the call site requires more
documentation.*"

Finally, we should also consider the possibility of being explicit about
some but not all type parameters. If allowed, something like "only trailing
type parameters could be missing" would be necessary to avoid ambiguity.

All the best,
Ramiro

···

On Tue, 29 Nov 2016 at 17:11 Douglas Gregor <dgregor@apple.com> wrote:

On Nov 21, 2016, at 3:05 PM, Ramiro Feria Purón via swift-evolution < > swift-evolution@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.

I’d like one bit of clarification in the proposal. Right now, one is not
permitted to have a type parameter in a generic function that isn’t used
somewhere in its signature, e.g.,

func f<T>() -> Void { … } // error: T is not part of the signature of f()

This restriction is obvious in today’s Swift, because there is absolutely
no way one could ever use this function. With your proposed extension, it
would be possible to use this function. Does the restriction remain or is
it lifted?

Personally, I’d like the restriction to stay, because it feels like such
functions fall into the same camp as unsafeBitCast: if the type parameter
affects how the function operates but is *not* part of its signature, then
it should be expressed like a normal parameter (of a metatype). It also
helps provide better diagnostics when changing a generic function to no
longer require one of its type parameters.

And, as Dave notes, it’s effectively syntactic sugar, so it belongs in
Swift 4 stage 2.

- Doug


(Dave Abrahams) #14

Yeah, but IMO ideally we'd have a way to inhibit deduction of some
generic type parameters. I might even be willing to inhibit deduction,
by default, of all generic function type parameters that don't appear in
the parameter list.

···

on Mon Nov 28 2016, Douglas Gregor <swift-evolution@swift.org> wrote:

On Nov 21, 2016, at 3:05 PM, Ramiro Feria Purón via swift-evolution > <swift-evolution@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.

--
-Dave


(Adrian Zubarev) #15

You forgot to mention Brent. :wink:

···

--
Adrian Zubarev
Sent with Airmail

Am 1. Dezember 2016 um 15:03:07, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

I disagree with the suggestion. Unlike C++'s templates, a Swift's generic function is semantically a single function. One can say that together with a metatype parameter, we pass in witness table.

What I think we should do is make metatypes easier to use. Adrian and I have a PR that has already been waiting for almost a month: https://github.com/apple/swift-evolution/pull/553
Plus, removal of .self could simplify such calls.


(Goffredo Marocchi) #16

It seems odd that no review or comment has been made as the PR seems a step in the right direction.

···

Sent from my iPhone

On 1 Dec 2016, at 14:17, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

You forgot to mention Brent. :wink:

--
Adrian Zubarev
Sent with Airmail

Am 1. Dezember 2016 um 15:03:07, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

I disagree with the suggestion. Unlike C++'s templates, a Swift's generic function is semantically a single function. One can say that together with a metatype parameter, we pass in witness table.

What I think we should do is make metatypes easier to use. Adrian and I have a PR that has already been waiting for almost a month: https://github.com/apple/swift-evolution/pull/553
Plus, removal of .self could simplify such calls.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Ramiro Feria Purón) #17

*Unlike C++'s templates, a Swift's generic function is semantically a
single function.*

Anton, could you provide further insight on this?

···

On Fri, 2 Dec 2016 at 01:03 Anton Zhilin via swift-evolution < swift-evolution@swift.org> wrote:

I disagree with the suggestion. Unlike C++'s templates, a Swift's generic
function is semantically a single function. One can say that together with
a metatype parameter, we pass in witness table.

What I think we should do is make metatypes easier to use. *Adrian and I
have a PR* that has already been waiting for almost a month:
https://github.com/apple/swift-evolution/pull/553
Plus, removal of .self could simplify such calls.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Derrick Ho) #18

If you want to feed it a type rather than infer it from variable type or cast it using as! Then you can just add the type as a parameter…

func processAll<T: Vehicle>(type: T.Type, in vehicles: [Vehicle], condition: (Vehicle) -> Bool) -> [T] {
  
  return []
}

processAll(type: Bicycle.self, in: [Vehicle()], condition: {_ in true})

···

On Nov 21, 2016, at 8:24 PM, Ramiro Feria Purón via swift-evolution <swift-evolution@swift.org> wrote:

Yes, it does; as it does let processedCars: [Car] = processAll(in: vehicles, condition: aboveSpeedLimit)

The core of the matter is that it is nowhere stated in the call (expression) processAll(in: vehicles, condition: aboveSpeedLimit) that the specialised version processAll<Bicycle> is the one to be called.

Although we are aware of the semantics, it does not read naturally and is somehow confusing. The ultimate goal is to make swift cleaner and clear. It rather feels like invoking non-specialised, non-generic function, and then casting the result to an array of a given type, which is not the case.

All the best,
Ramiro

2016-11-22 12:09 GMT+11:00 Sean Heber <sean@fifthace.com <mailto:sean@fifthace.com>>:
Doesn't this work?

processAll(in: vehicles, condition: aboveSpeedLimit) as [Bicycle]

l8r
Sean

Sent from my iPad

On Nov 21, 2016, at 6:11 PM, Ramiro Feria Purón via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Inferring the type of an expression from its surrounding context is not necessarily an ideal feature in a strongly typed programming language.

The goal here is to be able to be explicit about the type, in an elegant and clear way, especially when there is no (need for a) context.

Note that the <Type> "noise" would only be necessary when the type could not be inferred from the actual parameters (and if the current surrounding context type inference was removed, which is not part of the proposal).

The line of code from the Motivation that does not compile is:

processAll<Bicycle>(in: vehicles, condition: aboveSpeedLimit) // This should be allowed under this proposal

2016-11-22 10:35 GMT+11:00 Adrian Zubarev <adrian.zubarev@devandartist.com <mailto:adrian.zubarev@devandartist.com>>:
I’m not sure what are you trying to solve here. Your code from the motivation compiles just fine in my Playground.

About the g function:

g(7) as [String]
let _: [String] = g(7)
Personally I don’t like to see <Type> noise on functions in Swift.

--
Adrian Zubarev
Sent with Airmail

Am 22. November 2016 um 00:06:02, Ramiro Feria Purón via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

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.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Ramiro Feria Purón) #19

Derrick,

This proposal is nothing but adding built-in language support for the code
you have just written.

Regards,
Ramiro

···

2016-11-22 12:40 GMT+11:00 Derrick Ho <wh1pch81n@gmail.com>:

If you want to feed it a type rather than infer it from variable type or
cast it using as! Then you can just add the type as a parameter…

func processAll<T: Vehicle>(type: T.Type, in vehicles: [Vehicle],
condition: (Vehicle) -> Bool) -> [T] {

return []
}

processAll(type: Bicycle.self, in: [Vehicle()], condition: {_ in true})

On Nov 21, 2016, at 8:24 PM, Ramiro Feria Purón via swift-evolution < > swift-evolution@swift.org> wrote:

Yes, it does; as it does let processedCars: [Car] = processAll(in:
vehicles, condition: aboveSpeedLimit)

The core of the matter is that it is nowhere stated in the call
(expression) processAll(in: vehicles, condition: aboveSpeedLimit) that
the specialised version processAll<Bicycle> is the one to be called.

Although we are aware of the semantics, it does not read naturally and is
somehow confusing. The ultimate goal is to make swift cleaner and clear. It
rather feels like invoking non-specialised, non-generic function, and then
casting the result to an array of a given type, which is not the case.

All the best,
Ramiro

2016-11-22 12:09 GMT+11:00 Sean Heber <sean@fifthace.com>:

Doesn't this work?

processAll(in: vehicles, condition: aboveSpeedLimit) as [Bicycle]

l8r
Sean

Sent from my iPad

On Nov 21, 2016, at 6:11 PM, Ramiro Feria Purón via swift-evolution < >> swift-evolution@swift.org> wrote:

Inferring the type of an expression from its surrounding context is not
necessarily an ideal feature in a strongly typed programming language.

The goal here is to be able to be explicit about the type, in an elegant
and clear way, especially when there is no (need for a) context.

Note that the <Type> "noise" would only be necessary when the type could
not be inferred from the actual parameters (and if the current surrounding
context type inference was removed, which is not part of the proposal).

The line of code from the Motivation that does not compile is:

processAll<Bicycle>(in: vehicles, condition: aboveSpeedLimit)
            // This should be allowed under this proposal

2016-11-22 10:35 GMT+11:00 Adrian Zubarev <adrian.zubarev@devandartist.c
>:

I’m not sure what are you trying to solve here. Your code from the
motivation compiles just fine in my Playground.

About the g function:

g(7) as [String]
let _: [String] = g(7)

Personally I don’t like to see <Type> noise on functions in Swift.

--
Adrian Zubarev
Sent with Airmail

Am 22. November 2016 um 00:06:02, Ramiro Feria Purón via swift-evolution
(swift-evolution@swift.org) schrieb:

*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.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Goffredo Marocchi) #20

I think this is a case where the angle bran jets is both more readable and terse without losing context... opinions and all :).

···

Sent from my iPhone

On 29 Nov 2016, at 09:47, Andrew Trick via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 28, 2016, at 10:11 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 21, 2016, at 3:05 PM, Ramiro Feria Purón via swift-evolution <swift-evolution@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…

Angle brackets in function calls are hideous. This is objectively more clear and much prettier IMO:

  unsafeBitCast(something, to: Int)

it would enable type inference to go wild with unsafe casts:

  foo(unsafeBitCast(something)) // just cast it to.. whatever

which is… not great.

I’d like one bit of clarification in the proposal. Right now, one is not permitted to have a type parameter in a generic function that isn’t used somewhere in its signature, e.g.,

  func f<T>() -> Void { … } // error: T is not part of the signature of f()

This restriction is obvious in today’s Swift, because there is absolutely no way one could ever use this function. With your proposed extension, it would be possible to use this function. Does the restriction remain or is it lifted?

Personally, I’d like the restriction to stay, because it feels like such functions fall into the same camp as unsafeBitCast: if the type parameter affects how the function operates but is *not* part of its signature, then it should be expressed like a normal parameter (of a metatype). It also helps provide better diagnostics when changing a generic function to no longer require one of its type parameters.

+1 for required type parameters being normal parameters.

I think the case mentioned in the proposal reads much better as:

  processAll(in: vehicles, as: Bicycle, condition: aboveSpeedLimit)

If angle brackets can be limited to generic definitions and type names, that’s a great accomplishment.

-Andy

And, as Dave notes, it’s effectively syntactic sugar, so it belongs in Swift 4 stage 2.

  - Doug

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution