Allow explicit specialization of generic functions

Hello,

This is a new pitch to allow explicitly specializing generic functions. Notice that potential ambiguity with initialisers and how I’m currently trying to avoid it. Please let me know what you think!

David

Allow explicit specialization of generic functions

Proposal: SE-XXXX <https://github.com/apple/swift-evolution/blob/master/proposals/XXXX-allow-explicit-specialization-generic-functions.md&gt;
Author: David Hart <https://github.com/hartbit&gt;, Douglas Gregor <https://github.com/DougGregor&gt;
Status: TBD
Review manager: TBD
<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions

This proposal allows bypassing the type inference engine and explicitly specializing type arguments of generic functions.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions

In Swift, generic type parameters are inferred by the argument or return value types as follows:

func foo<T>(t: T) { ... }

foo(5) // infers T = Int
There exists certain scenarios when a programmer wants to explicitly specialize a generic function. Swift does not allow it, so we resort to giving hints to the inference engine:

let f1 = foo as ((Int) -> Void)
let f2: (Int) -> Void = foo
let f3 = foo<Int> // error: Cannot explicitly specialize a generic function

func bar<T>() -> T { ... }

let b1 = bar() as Int
let b2: Int = bar()
let b3 = bar<Int>() // error: Cannot explicitly specialize a generic function
This behaviour is not very consistent with generic types which allow specialization:

let array: Array<Int> = Array<Int>(arrayLiteral: 1, 2, 3)
Therefore, this proposal seeks to make the above errors valid specializations:

let f3 = foo<Int> // explicitly specialized to (Int) -> Void
let b3 = bar<Int>() // explicitly specialized to () -> Int
An ambiguous scenario arrises when we wish to specialize initializer functions:

struct Foo<T: RawRepresentable where T.RawValue == String> {
    let storage: T

    init<U: CustomStringConvertible>(_ value: U) {
        storage = T(rawValue: value.description)!
    }
}

enum Bar: String, CustomStringConvertible {
    case foobar = "foo"

    var description: String {
        return self.rawValue
    }
}

let a = Foo<Bar>(Bar.foobar)
Does this specialization specialize the struct's or the initializer's generic type? The proposal solves this ambiguity by requiring initializer generic type specialization to use the init syntax:

let a = Foo<Bar>.init<Bar>(Bar.foobar)
<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions Design

Function calls are fairly straight forward and have their grammar modified as follows:

function-call-expression → postfix-expression­ generic-argument-clause­opt parenthesized-expression

function-call-expression → postfix-expression generic-argument-clause­opt ­parenthesized-expression­opt ­trailing-closure­

To allow initializers to be called with explicit specialization, we need to use the Initializer Expression. Its grammar is modified to:

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt ( ­argument-names­ )

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions on Existing Code

This proposal is purely additive and will have no impact on existing code.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions Considered

Not adopting this proposal for Swift.

4 Likes

This is a new pitch to allow explicitly specializing generic functions. Notice that potential ambiguity with initialisers and how I’m currently trying to avoid it. Please let me know what you think!

+1, including your `init` trick.

···

--
Brent Royal-Gordon
Architechies

2 Likes

Hello,

This is a new pitch to allow explicitly specializing generic functions. Notice that potential ambiguity with initialisers and how I’m currently trying to avoid it. Please let me know what you think!

Hi David,

I’m wondering if the only motivation here is to be more explicit about the types involved so you can avoid having to rely on type inference and the code is more clear, or if you also have the expectation that we’ll generate a type-specialized version of the body of the function in question which you would expect to be more efficient than the fully generic version?

Do you imagine this supporting a scenario where only some of the type parameters are given explicitly and others are inferred, or would this only be supported in the case where all type parameters were given explicitly?

What if some of those type parameters were themselves generics? For example:
  func callee<T : SignedInteger, U : AnotherProtocol>(x: T, y: U) { … }
  func caller<T : AnotherProtocol>(x: T) {
    let f = bar<Int, T> // supported
    f(3, x)
  }

Mark

···

On May 25, 2016, at 4:17 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

David

Allow explicit specialization of generic functions

Proposal: SE-XXXX <https://github.com/apple/swift-evolution/blob/master/proposals/XXXX-allow-explicit-specialization-generic-functions.md&gt;
Author: David Hart <https://github.com/hartbit&gt;, Douglas Gregor <https://github.com/DougGregor&gt;
Status: TBD
Review manager: TBD
<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions

This proposal allows bypassing the type inference engine and explicitly specializing type arguments of generic functions.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions

In Swift, generic type parameters are inferred by the argument or return value types as follows:

func foo<T>(t: T) { ... }

foo(5) // infers T = Int
There exists certain scenarios when a programmer wants to explicitly specialize a generic function. Swift does not allow it, so we resort to giving hints to the inference engine:

let f1 = foo as ((Int) -> Void)
let f2: (Int) -> Void = foo
let f3 = foo<Int> // error: Cannot explicitly specialize a generic function

func bar<T>() -> T { ... }

let b1 = bar() as Int
let b2: Int = bar()
let b3 = bar<Int>() // error: Cannot explicitly specialize a generic function
This behaviour is not very consistent with generic types which allow specialization:

let array: Array<Int> = Array<Int>(arrayLiteral: 1, 2, 3)
Therefore, this proposal seeks to make the above errors valid specializations:

let f3 = foo<Int> // explicitly specialized to (Int) -> Void
let b3 = bar<Int>() // explicitly specialized to () -> Int
An ambiguous scenario arrises when we wish to specialize initializer functions:

struct Foo<T: RawRepresentable where T.RawValue == String> {
    let storage: T

    init<U: CustomStringConvertible>(_ value: U) {
        storage = T(rawValue: value.description)!
    }
}

enum Bar: String, CustomStringConvertible {
    case foobar = "foo"

    var description: String {
        return self.rawValue
    }
}

let a = Foo<Bar>(Bar.foobar)
Does this specialization specialize the struct's or the initializer's generic type? The proposal solves this ambiguity by requiring initializer generic type specialization to use the init syntax:

let a = Foo<Bar>.init<Bar>(Bar.foobar)
<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions Design

Function calls are fairly straight forward and have their grammar modified as follows:

function-call-expression → postfix-expression­ generic-argument-clause­opt parenthesized-expression

function-call-expression → postfix-expression generic-argument-clause­opt ­parenthesized-expression­opt ­trailing-closure­

To allow initializers to be called with explicit specialization, we need to use the Initializer Expression. Its grammar is modified to:

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt ( ­argument-names­ )

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions on Existing Code

This proposal is purely additive and will have no impact on existing code.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions Considered

Not adopting this proposal for Swift.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I would like explicit specialization as well.

For historical interest, there is a blurb in the generics design document describing the original rationale (https://github.com/apple/swift/blob/master/docs/Generics.rst, "Type Parameter Deduction").

Austin

···

On May 25, 2016, at 8:39 PM, David Sweeris via swift-evolution <swift-evolution@swift.org> wrote:

On May 25, 2016, at 6:17 PM, David Hart via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello,

This is a new pitch to allow explicitly specializing generic functions. Notice that potential ambiguity with initialisers and how I’m currently trying to avoid it. Please let me know what you think!

+1111!1eleventyone!

I’ve always wondered why that was disallowed in the first place… In any case, it’ll make it easier to work with functions in which the generic parameter only appears in the return signature. The current workaround is to pass in a “dummy” variable like so:
func foo <T> (_: T.Type) -> T {}
let x = foo(Int.self)
or to use explicit type annotation:
func foo <T> () -> T {}
let y: Int = foo()

The first is annoying (although less so in Swift 3 since we won’t need the `.self` part anymore), the second requires creating a new variable which makes the code jarring, and the two are incompatible with each other since the signatures are different.

Also, I’d think it’d simplify the compiler, since there’d be one less error condition.

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

I have an alternative you might like to consider: type deduction operators

The input type deduction operator lets you put one or more types in front
of a function to guide type deduction based on the parameters

The output type deduction operator lets you put a type after a function to
guide type deduction based on the return type

This is a library-only solution that lets you not only select a
specialization for a generic function, but also choose an overload from an
overload set

It's up to the user whether they use input, output, or both type deduction
ops and up to them how many types they supply for input. For example, when
you know that the overloads or generic functions you're choosing from have
two parameters of the same type, you only need to provide a single type to
trigger the correct type deduction (shown below with operator+).

Here's the basic idea (the specific symbol used is just what I use, could
be changed):

infix operator >>> { associativity left }

// Input type deduction operator
func >>> <In, Out>(deduce: In.Type, fn: In -> Out) -> In -> Out {
    return fn
}

// Add versions for functions with 2-5 parameters
func >>> <In, In2, Out>(deduce: In.Type, fn: (In, In2) -> Out) -> (In, In2)
-> Out {
    return fn
}

// Add versions for 2-5 inputs
func >>> <In, In2, Out>(deduce: (In.Type, In2.Type), fn: (In, In2) -> Out)
-> (In, In2) -> Out {
    return fn
}

// Output type deduction operator
func >>> <In, Out>(fn: In -> Out, deduce: Out.Type) -> In -> Out {
    return fn
}

let plus1 = Int.self >>> (+)
let plus2 = (Int.self, Int.self) >>> (+)

-- Callionica

···

On Wed, May 25, 2016 at 4:17 PM, David Hart via swift-evolution < swift-evolution@swift.org> wrote:

Hello,

This is a new pitch to allow explicitly specializing generic functions.
Notice that potential ambiguity with initialisers and how I’m currently
trying to avoid it. Please let me know what you think!

David

Allow explicit specialization of generic functions

   - Proposal: SE-XXXX
   <https://github.com/apple/swift-evolution/blob/master/proposals/XXXX-allow-explicit-specialization-generic-functions.md&gt;
   - Author: David Hart <https://github.com/hartbit&gt;, Douglas Gregor
   <https://github.com/DougGregor&gt;
   - Status: TBD
   - Review manager: TBD

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions;
Introduction

This proposal allows bypassing the type inference engine and explicitly
specializing type arguments of generic functions.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions;
Motivation

In Swift, generic type parameters are inferred by the argument or return
value types as follows:

func foo<T>(t: T) { ... }

foo(5) // infers T = Int

There exists certain scenarios when a programmer wants to explicitly
specialize a generic function. Swift does not allow it, so we resort to
giving hints to the inference engine:

let f1 = foo as ((Int) -> Void)let f2: (Int) -> Void = foolet f3 = foo<Int> // error: Cannot explicitly specialize a generic function
func bar<T>() -> T { ... }
let b1 = bar() as Intlet b2: Int = bar()let b3 = bar<Int>() // error: Cannot explicitly specialize a generic function

This behaviour is not very consistent with generic types which allow
specialization:

let array: Array<Int> = Array<Int>(arrayLiteral: 1, 2, 3)

Therefore, this proposal seeks to make the above errors valid
specializations:

let f3 = foo<Int> // explicitly specialized to (Int) -> Void let b3 = bar<Int>() // explicitly specialized to () -> Int

An ambiguous scenario arrises when we wish to specialize initializer
functions:

struct Foo<T: RawRepresentable where T.RawValue == String> {
    let storage: T

    init<U: CustomStringConvertible>(_ value: U) {
        storage = T(rawValue: value.description)!
    }
}
enum Bar: String, CustomStringConvertible {
    case foobar = "foo"

    var description: String {
        return self.rawValue
    }
}
let a = Foo<Bar>(Bar.foobar)

Does this specialization specialize the struct's or the initializer's
generic type? The proposal solves this ambiguity by requiring initializer
generic type specialization to use the init syntax:

let a = Foo<Bar>.init<Bar>(Bar.foobar)

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions
Design

Function calls are fairly straight forward and have their grammar modified
as follows:

*function-call-expression* → *postfix-expression­*
*generic-argument-clause­opt* *parenthesized-expression*

*function-call-expression* → *postfix-expression*
*generic-argument-clause­opt* *­parenthesized-expression­opt*
*­trailing-closure­*

To allow initializers to be called with explicit specialization, we need
to use the Initializer Expression. Its grammar is modified to:

*initializer-expression* → *postfix-expression­* . *­init­*
*generic-argument-clause­opt*

*initializer-expression* → *postfix-expression­* . *­init­*
*generic-argument-clause­opt* ( *­argument-names­* )

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions
on Existing Code

This proposal is purely additive and will have no impact on existing code.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions
Considered
Not adopting this proposal for Swift.

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

1 Like

+1111!1eleventyone!

I’ve always wondered why that was disallowed in the first place… In any case, it’ll make it easier to work with functions in which the generic parameter only appears in the return signature. The current workaround is to pass in a “dummy” variable like so:
func foo <T> (_: T.Type) -> T {}
let x = foo(Int.self)
or to use explicit type annotation:
func foo <T> () -> T {}
let y: Int = foo()

The first is annoying (although less so in Swift 3 since we won’t need the `.self` part anymore), the second requires creating a new variable which makes the code jarring, and the two are incompatible with each other since the signatures are different.

Also, I’d think it’d simplify the compiler, since there’d be one less error condition.

- Dave Sweeris

···

On May 25, 2016, at 6:17 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

Hello,

This is a new pitch to allow explicitly specializing generic functions. Notice that potential ambiguity with initialisers and how I’m currently trying to avoid it. Please let me know what you think!

1 Like

+1. There are times where the compiler can't infer the type and gives weird errors (mostly due to inferring the type wrong). Making this explicit would help a lot.

···

On May 26, 2016, at 1:17 AM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

Hello,

This is a new pitch to allow explicitly specializing generic functions. Notice that potential ambiguity with initialisers and how I’m currently trying to avoid it. Please let me know what you think!

David

Allow explicit specialization of generic functions

Proposal: SE-XXXX <https://github.com/apple/swift-evolution/blob/master/proposals/XXXX-allow-explicit-specialization-generic-functions.md&gt;
Author: David Hart <https://github.com/hartbit&gt;, Douglas Gregor <https://github.com/DougGregor&gt;
Status: TBD
Review manager: TBD
<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions

This proposal allows bypassing the type inference engine and explicitly specializing type arguments of generic functions.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions

In Swift, generic type parameters are inferred by the argument or return value types as follows:

func foo<T>(t: T) { ... }

foo(5) // infers T = Int
There exists certain scenarios when a programmer wants to explicitly specialize a generic function. Swift does not allow it, so we resort to giving hints to the inference engine:

let f1 = foo as ((Int) -> Void)
let f2: (Int) -> Void = foo
let f3 = foo<Int> // error: Cannot explicitly specialize a generic function

func bar<T>() -> T { ... }

let b1 = bar() as Int
let b2: Int = bar()
let b3 = bar<Int>() // error: Cannot explicitly specialize a generic function
This behaviour is not very consistent with generic types which allow specialization:

let array: Array<Int> = Array<Int>(arrayLiteral: 1, 2, 3)
Therefore, this proposal seeks to make the above errors valid specializations:

let f3 = foo<Int> // explicitly specialized to (Int) -> Void
let b3 = bar<Int>() // explicitly specialized to () -> Int
An ambiguous scenario arrises when we wish to specialize initializer functions:

struct Foo<T: RawRepresentable where T.RawValue == String> {
    let storage: T

    init<U: CustomStringConvertible>(_ value: U) {
        storage = T(rawValue: value.description)!
    }
}

enum Bar: String, CustomStringConvertible {
    case foobar = "foo"

    var description: String {
        return self.rawValue
    }
}

let a = Foo<Bar>(Bar.foobar)
Does this specialization specialize the struct's or the initializer's generic type? The proposal solves this ambiguity by requiring initializer generic type specialization to use the init syntax:

let a = Foo<Bar>.init<Bar>(Bar.foobar)
<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions Design

Function calls are fairly straight forward and have their grammar modified as follows:

function-call-expression → postfix-expression­ generic-argument-clause­opt parenthesized-expression

function-call-expression → postfix-expression generic-argument-clause­opt ­parenthesized-expression­opt ­trailing-closure­

To allow initializers to be called with explicit specialization, we need to use the Initializer Expression. Its grammar is modified to:

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt ( ­argument-names­ )

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions on Existing Code

This proposal is purely additive and will have no impact on existing code.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions Considered

Not adopting this proposal for Swift.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

The former. This proposal is simply to make certain scenarios which require to be explicit to be more elegant and more in line with generic types.

Moreover, what you suggest might be dangerous. Imagine this code:

func foo<T, U>() -> (T, U) { ... }
let a = foo<Int, Foobarr>()

I just did a typo on Foobarr and Foobarr does not exist. So the compiler has to guess that I meant an inferred generic type, which might not be what I want. And I don't think that the compiler should require us to remember the actual name of the generic types.

···

On 26 May 2016, at 03:38, Mark Lacey <mark.lacey@apple.com> wrote:

On May 25, 2016, at 4:17 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

Hello,

This is a new pitch to allow explicitly specializing generic functions. Notice that potential ambiguity with initialisers and how I’m currently trying to avoid it. Please let me know what you think!

Hi David,

I’m wondering if the only motivation here is to be more explicit about the types involved so you can avoid having to rely on type inference and the code is more clear, or if you also have the expectation that we’ll generate a type-specialized version of the body of the function in question which you would expect to be more efficient than the fully generic version?

Do you imagine this supporting a scenario where only some of the type parameters are given explicitly and others are inferred, or would this only be supported in the case where all type parameters were given explicitly?

What if some of those type parameters were themselves generics? For example:
  func callee<T : SignedInteger, U : AnotherProtocol>(x: T, y: U) { … }
  func caller<T : AnotherProtocol>(x: T) {
    let f = bar<Int, T> // supported
    f(3, x)
  }

Mark

David

Allow explicit specialization of generic functions
Proposal: SE-XXXX
Author: David Hart, Douglas Gregor
Status: TBD
Review manager: TBD
Introduction

This proposal allows bypassing the type inference engine and explicitly specializing type arguments of generic functions.

Motivation

In Swift, generic type parameters are inferred by the argument or return value types as follows:

func foo<T>(t: T) { ... }

foo(5) // infers T = Int
There exists certain scenarios when a programmer wants to explicitly specialize a generic function. Swift does not allow it, so we resort to giving hints to the inference engine:

let f1 = foo as ((Int) -> Void)
let f2: (Int) -> Void = foo
let f3 = foo<Int> // error: Cannot explicitly specialize a generic function

func bar<T>() -> T { ... }

let b1 = bar() as Int
let b2: Int = bar()
let b3 = bar<Int>() // error: Cannot explicitly specialize a generic function
This behaviour is not very consistent with generic types which allow specialization:

let array: Array<Int> = Array<Int>(arrayLiteral: 1, 2, 3)
Therefore, this proposal seeks to make the above errors valid specializations:

let f3 = foo<Int> // explicitly specialized to (Int) -> Void
let b3 = bar<Int>() // explicitly specialized to () -> Int
An ambiguous scenario arrises when we wish to specialize initializer functions:

struct Foo<T: RawRepresentable where T.RawValue == String> {
    let storage: T

    init<U: CustomStringConvertible>(_ value: U) {
        storage = T(rawValue: value.description)!
    }
}

enum Bar: String, CustomStringConvertible {
    case foobar = "foo"

    var description: String {
        return self.rawValue
    }
}

let a = Foo<Bar>(Bar.foobar)
Does this specialization specialize the struct's or the initializer's generic type? The proposal solves this ambiguity by requiring initializer generic type specialization to use the init syntax:

let a = Foo<Bar>.init<Bar>(Bar.foobar)
Detailed Design

Function calls are fairly straight forward and have their grammar modified as follows:

function-call-expression → postfix-expression­ generic-argument-clause­opt parenthesized-expression

function-call-expression → postfix-expression generic-argument-clause­opt ­parenthesized-expression­opt ­trailing-closure­

To allow initializers to be called with explicit specialization, we need to use the Initializer Expression. Its grammar is modified to:

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt ( ­argument-names­ )

Impact on Existing Code

This proposal is purely additive and will have no impact on existing code.

Alternatives Considered

Not adopting this proposal for Swift.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I personally don't really see the advantage of those deduction operators. I would prefer writing:

let plus1: (Int, Int) -> Int = +
let plus2 = + as ((Int, Int) -> Int)

···

On 26 May 2016, at 06:11, Callionica (Swift) <swift-callionica@callionica.com> wrote:

I have an alternative you might like to consider: type deduction operators

The input type deduction operator lets you put one or more types in front of a function to guide type deduction based on the parameters

The output type deduction operator lets you put a type after a function to guide type deduction based on the return type

This is a library-only solution that lets you not only select a specialization for a generic function, but also choose an overload from an overload set

It's up to the user whether they use input, output, or both type deduction ops and up to them how many types they supply for input. For example, when you know that the overloads or generic functions you're choosing from have two parameters of the same type, you only need to provide a single type to trigger the correct type deduction (shown below with operator+).

Here's the basic idea (the specific symbol used is just what I use, could be changed):

infix operator >>> { associativity left }

// Input type deduction operator
func >>> <In, Out>(deduce: In.Type, fn: In -> Out) -> In -> Out {
    return fn
}

// Add versions for functions with 2-5 parameters
func >>> <In, In2, Out>(deduce: In.Type, fn: (In, In2) -> Out) -> (In, In2) -> Out {
    return fn
}

// Add versions for 2-5 inputs
func >>> <In, In2, Out>(deduce: (In.Type, In2.Type), fn: (In, In2) -> Out) -> (In, In2) -> Out {
    return fn
}

// Output type deduction operator
func >>> <In, Out>(fn: In -> Out, deduce: Out.Type) -> In -> Out {
    return fn
}

let plus1 = Int.self >>> (+)
let plus2 = (Int.self, Int.self) >>> (+)

-- Callionica

On Wed, May 25, 2016 at 4:17 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:
Hello,

This is a new pitch to allow explicitly specializing generic functions. Notice that potential ambiguity with initialisers and how I’m currently trying to avoid it. Please let me know what you think!

David

Allow explicit specialization of generic functions
Proposal: SE-XXXX
Author: David Hart, Douglas Gregor
Status: TBD
Review manager: TBD
Introduction

This proposal allows bypassing the type inference engine and explicitly specializing type arguments of generic functions.

Motivation

In Swift, generic type parameters are inferred by the argument or return value types as follows:

func foo<T>(t: T) { ... }

foo(5) // infers T = Int
There exists certain scenarios when a programmer wants to explicitly specialize a generic function. Swift does not allow it, so we resort to giving hints to the inference engine:

let f1 = foo as ((Int) -> Void)
let f2: (Int) -> Void = foo
let f3 = foo<Int> // error: Cannot explicitly specialize a generic function

func bar<T>() -> T { ... }

let b1 = bar() as Int
let b2: Int = bar()
let b3 = bar<Int>() // error: Cannot explicitly specialize a generic function
This behaviour is not very consistent with generic types which allow specialization:

let array: Array<Int> = Array<Int>(arrayLiteral: 1, 2, 3)
Therefore, this proposal seeks to make the above errors valid specializations:

let f3 = foo<Int> // explicitly specialized to (Int) -> Void
let b3 = bar<Int>() // explicitly specialized to () -> Int
An ambiguous scenario arrises when we wish to specialize initializer functions:

struct Foo<T: RawRepresentable where T.RawValue == String> {
    let storage: T

    init<U: CustomStringConvertible>(_ value: U) {
        storage = T(rawValue: value.description)!
    }
}

enum Bar: String, CustomStringConvertible {
    case foobar = "foo"

    var description: String {
        return self.rawValue
    }
}

let a = Foo<Bar>(Bar.foobar)
Does this specialization specialize the struct's or the initializer's generic type? The proposal solves this ambiguity by requiring initializer generic type specialization to use the init syntax:

let a = Foo<Bar>.init<Bar>(Bar.foobar)
Detailed Design

Function calls are fairly straight forward and have their grammar modified as follows:

function-call-expression → postfix-expression­ generic-argument-clause­opt parenthesized-expression

function-call-expression → postfix-expression generic-argument-clause­opt ­parenthesized-expression­opt ­trailing-closure­

To allow initializers to be called with explicit specialization, we need to use the Initializer Expression. Its grammar is modified to:

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt ( ­argument-names­ )

Impact on Existing Code

This proposal is purely additive and will have no impact on existing code.

Alternatives Considered

Not adopting this proposal for Swift.

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

1 Like

Sounds good to me. So it matches the order of the generic parameters?

e.g.

func foo<T, U>(t: T, u: U) { … }

let f1 = foo<Int, String>

So if the function declaration was changed to swap T and U, it would break, same as say a generic struct type.

Patrick

+1

···

On Wed, May 25, 2016 at 7:50 PM, Patrick Smith via swift-evolution < swift-evolution@swift.org> wrote:

Sounds good to me. So it matches the order of the generic parameters?

e.g.

func foo<T, U>(t: T, u: U) { … }

let f1 = foo<Int, String>

So if the function declaration was changed to swap T and U, it would
break, same as say a generic struct type.

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

The former. This proposal is simply to make certain scenarios which require to be explicit to be more elegant and more in line with generic types.

Moreover, what you suggest might be dangerous. Imagine this code:

func foo<T, U>() -> (T, U) { ... }
let a = foo<Int, Foobarr>()

I just did a typo on Foobarr and Foobarr does not exist. So the compiler has to guess that I meant an inferred generic type, which might not be what I want. And I don't think that the compiler should require us to remember the actual name of the generic types.

I probably wasn’t clear enough here, but what I meant was something like:
  func foo<T, U>(x: U) -> (T, U) { … }

  let a = foo<Int, ...>(v) // the second type was unspecified, but can be inferred by the argument ‘v'

Ignore the specific syntax for the unspecified type here. Would your expectation be that something like this work as well, or does one need to specify all-or-none when it comes to the type parameters?

If the former, what is the proposed syntax for what goes in the positions of the types that are unspecified?

Mark

···

On May 25, 2016, at 11:37 PM, David Hart <david@hartbit.com> wrote:

On 26 May 2016, at 03:38, Mark Lacey <mark.lacey@apple.com <mailto:mark.lacey@apple.com>> wrote:

On May 25, 2016, at 4:17 PM, David Hart via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello,

This is a new pitch to allow explicitly specializing generic functions. Notice that potential ambiguity with initialisers and how I’m currently trying to avoid it. Please let me know what you think!

Hi David,

I’m wondering if the only motivation here is to be more explicit about the types involved so you can avoid having to rely on type inference and the code is more clear, or if you also have the expectation that we’ll generate a type-specialized version of the body of the function in question which you would expect to be more efficient than the fully generic version?

Do you imagine this supporting a scenario where only some of the type parameters are given explicitly and others are inferred, or would this only be supported in the case where all type parameters were given explicitly?

What if some of those type parameters were themselves generics? For example:
  func callee<T : SignedInteger, U : AnotherProtocol>(x: T, y: U) { … }
  func caller<T : AnotherProtocol>(x: T) {
    let f = bar<Int, T> // supported
    f(3, x)
  }

Mark

David

Allow explicit specialization of generic functions

Proposal: SE-XXXX <https://github.com/apple/swift-evolution/blob/master/proposals/XXXX-allow-explicit-specialization-generic-functions.md&gt;
Author: David Hart <https://github.com/hartbit&gt;, Douglas Gregor <https://github.com/DougGregor&gt;
Status: TBD
Review manager: TBD
<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions

This proposal allows bypassing the type inference engine and explicitly specializing type arguments of generic functions.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions

In Swift, generic type parameters are inferred by the argument or return value types as follows:

func foo<T>(t: T) { ... }

foo(5) // infers T = Int
There exists certain scenarios when a programmer wants to explicitly specialize a generic function. Swift does not allow it, so we resort to giving hints to the inference engine:

let f1 = foo as ((Int) -> Void)
let f2: (Int) -> Void = foo
let f3 = foo<Int> // error: Cannot explicitly specialize a generic function

func bar<T>() -> T { ... }

let b1 = bar() as Int
let b2: Int = bar()
let b3 = bar<Int>() // error: Cannot explicitly specialize a generic function
This behaviour is not very consistent with generic types which allow specialization:

let array: Array<Int> = Array<Int>(arrayLiteral: 1, 2, 3)
Therefore, this proposal seeks to make the above errors valid specializations:

let f3 = foo<Int> // explicitly specialized to (Int) -> Void
let b3 = bar<Int>() // explicitly specialized to () -> Int
An ambiguous scenario arrises when we wish to specialize initializer functions:

struct Foo<T: RawRepresentable where T.RawValue == String> {
    let storage: T

    init<U: CustomStringConvertible>(_ value: U) {
        storage = T(rawValue: value.description)!
    }
}

enum Bar: String, CustomStringConvertible {
    case foobar = "foo"

    var description: String {
        return self.rawValue
    }
}

let a = Foo<Bar>(Bar.foobar)
Does this specialization specialize the struct's or the initializer's generic type? The proposal solves this ambiguity by requiring initializer generic type specialization to use the init syntax:

let a = Foo<Bar>.init<Bar>(Bar.foobar)
<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions Design

Function calls are fairly straight forward and have their grammar modified as follows:

function-call-expression → postfix-expression­ generic-argument-clause­opt parenthesized-expression

function-call-expression → postfix-expression generic-argument-clause­opt ­parenthesized-expression­opt ­trailing-closure­

To allow initializers to be called with explicit specialization, we need to use the Initializer Expression. Its grammar is modified to:

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt ( ­argument-names­ )

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions on Existing Code

This proposal is purely additive and will have no impact on existing code.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions Considered

Not adopting this proposal for Swift.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Yes, it matches the order of the generic parameters. I'll mention it explicitly in the next version.

···

On 26 May 2016, at 03:50, Patrick Smith <pgwsmith@gmail.com> wrote:

Sounds good to me. So it matches the order of the generic parameters?

e.g.

func foo<T, U>(t: T, u: U) { … }

let f1 = foo<Int, String>

So if the function declaration was changed to swap T and U, it would break, same as say a generic struct type.

Patrick

Sounds good :) I’ll mention those in the Alternatives.

···

On 26 May 2016, at 09:26, Callionica <cloud.google@callionica.com> wrote:

A perfectly reasonable preference. For me, "as" reads as a potential type conversion (which I don't want) and specifying the type on the local can be verbose in cases when a single input or output type is sufficient for disambiguation. plus1 has 1/3 the number of types specified in the type deduction operator example than appear in the explicitly typed local or the as-cast code. Anyway, I hope you will consider all hinting/deduction techniques as alternatives to your proposal so that you can say why it's better or if it's solving a different problem.

-- Callionica

On May 25, 2016, at 11:43 PM, David Hart <david@hartbit.com <mailto:david@hartbit.com>> wrote:

I personally don't really see the advantage of those deduction operators. I would prefer writing:

let plus1: (Int, Int) -> Int = +
let plus2 = + as ((Int, Int) -> Int)

On 26 May 2016, at 06:11, Callionica (Swift) <swift-callionica@callionica.com <mailto:swift-callionica@callionica.com>> wrote:

I have an alternative you might like to consider: type deduction operators

The input type deduction operator lets you put one or more types in front of a function to guide type deduction based on the parameters

The output type deduction operator lets you put a type after a function to guide type deduction based on the return type

This is a library-only solution that lets you not only select a specialization for a generic function, but also choose an overload from an overload set

It's up to the user whether they use input, output, or both type deduction ops and up to them how many types they supply for input. For example, when you know that the overloads or generic functions you're choosing from have two parameters of the same type, you only need to provide a single type to trigger the correct type deduction (shown below with operator+).

Here's the basic idea (the specific symbol used is just what I use, could be changed):

infix operator >>> { associativity left }

// Input type deduction operator
func >>> <In, Out>(deduce: In.Type, fn: In -> Out) -> In -> Out {
    return fn
}

// Add versions for functions with 2-5 parameters
func >>> <In, In2, Out>(deduce: In.Type, fn: (In, In2) -> Out) -> (In, In2) -> Out {
    return fn
}

// Add versions for 2-5 inputs
func >>> <In, In2, Out>(deduce: (In.Type, In2.Type), fn: (In, In2) -> Out) -> (In, In2) -> Out {
    return fn
}

// Output type deduction operator
func >>> <In, Out>(fn: In -> Out, deduce: Out.Type) -> In -> Out {
    return fn
}

let plus1 = Int.self >>> (+)
let plus2 = (Int.self, Int.self) >>> (+)

-- Callionica

On Wed, May 25, 2016 at 4:17 PM, David Hart via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Hello,

This is a new pitch to allow explicitly specializing generic functions. Notice that potential ambiguity with initialisers and how I’m currently trying to avoid it. Please let me know what you think!

David

Allow explicit specialization of generic functions

Proposal: SE-XXXX <https://github.com/apple/swift-evolution/blob/master/proposals/XXXX-allow-explicit-specialization-generic-functions.md&gt;
Author: David Hart <https://github.com/hartbit&gt;, Douglas Gregor <https://github.com/DougGregor&gt;
Status: TBD
Review manager: TBD
<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions

This proposal allows bypassing the type inference engine and explicitly specializing type arguments of generic functions.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions

In Swift, generic type parameters are inferred by the argument or return value types as follows:

func foo<T>(t: T) { ... }

foo(5) // infers T = Int
There exists certain scenarios when a programmer wants to explicitly specialize a generic function. Swift does not allow it, so we resort to giving hints to the inference engine:

let f1 = foo as ((Int) -> Void)
let f2: (Int) -> Void = foo
let f3 = foo<Int> // error: Cannot explicitly specialize a generic function

func bar<T>() -> T { ... }

let b1 = bar() as Int
let b2: Int = bar()
let b3 = bar<Int>() // error: Cannot explicitly specialize a generic function
This behaviour is not very consistent with generic types which allow specialization:

let array: Array<Int> = Array<Int>(arrayLiteral: 1, 2, 3)
Therefore, this proposal seeks to make the above errors valid specializations:

let f3 = foo<Int> // explicitly specialized to (Int) -> Void
let b3 = bar<Int>() // explicitly specialized to () -> Int
An ambiguous scenario arrises when we wish to specialize initializer functions:

struct Foo<T: RawRepresentable where T.RawValue == String> {
    let storage: T

    init<U: CustomStringConvertible>(_ value: U) {
        storage = T(rawValue: value.description)!
    }
}

enum Bar: String, CustomStringConvertible {
    case foobar = "foo"

    var description: String {
        return self.rawValue
    }
}

let a = Foo<Bar>(Bar.foobar)
Does this specialization specialize the struct's or the initializer's generic type? The proposal solves this ambiguity by requiring initializer generic type specialization to use the init syntax:

let a = Foo<Bar>.init<Bar>(Bar.foobar)
<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions Design

Function calls are fairly straight forward and have their grammar modified as follows:

function-call-expression → postfix-expression­ generic-argument-clause­opt parenthesized-expression

function-call-expression → postfix-expression generic-argument-clause­opt ­parenthesized-expression­opt ­trailing-closure­

To allow initializers to be called with explicit specialization, we need to use the Initializer Expression. Its grammar is modified to:

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt

initializer-expression → postfix-expression­ . ­init­ generic-argument-clause­opt ( ­argument-names­ )

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions on Existing Code

This proposal is purely additive and will have no impact on existing code.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions Considered

Not adopting this proposal for Swift.

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

A perfectly reasonable preference. For me, "as" reads as a potential type
conversion (which I don't want) and specifying the type on the local can be
verbose in cases when a single input or output type is sufficient for
disambiguation. plus1 has 1/3 the number of types specified in the type
deduction operator example than appear in the explicitly typed local or the
as-cast code. Anyway, I hope you will consider all hinting/deduction
techniques as alternatives to your proposal so that you can say why it's
better or if it's solving a different problem.

···

On Wed, May 25, 2016 at 11:43 PM, David Hart <david@hartbit.com> wrote:

I personally don't really see the advantage of those deduction operators.
I would prefer writing:

let plus1: (Int, Int) -> Int = +
let plus2 = + as ((Int, Int) -> Int)

On 26 May 2016, at 06:11, Callionica (Swift) < > swift-callionica@callionica.com> wrote:

I have an alternative you might like to consider: type deduction operators

The input type deduction operator lets you put one or more types in front
of a function to guide type deduction based on the parameters

The output type deduction operator lets you put a type after a function to
guide type deduction based on the return type

This is a library-only solution that lets you not only select a
specialization for a generic function, but also choose an overload from an
overload set

It's up to the user whether they use input, output, or both type deduction
ops and up to them how many types they supply for input. For example, when
you know that the overloads or generic functions you're choosing from have
two parameters of the same type, you only need to provide a single type to
trigger the correct type deduction (shown below with operator+).

Here's the basic idea (the specific symbol used is just what I use, could
be changed):

infix operator >>> { associativity left }

// Input type deduction operator
func >>> <In, Out>(deduce: In.Type, fn: In -> Out) -> In -> Out {
    return fn
}

// Add versions for functions with 2-5 parameters
func >>> <In, In2, Out>(deduce: In.Type, fn: (In, In2) -> Out) -> (In,
In2) -> Out {
    return fn
}

// Add versions for 2-5 inputs
func >>> <In, In2, Out>(deduce: (In.Type, In2.Type), fn: (In, In2) -> Out)
-> (In, In2) -> Out {
    return fn
}

// Output type deduction operator
func >>> <In, Out>(fn: In -> Out, deduce: Out.Type) -> In -> Out {
    return fn
}

let plus1 = Int.self >>> (+)
let plus2 = (Int.self, Int.self) >>> (+)

-- Callionica

On Wed, May 25, 2016 at 4:17 PM, David Hart via swift-evolution < > swift-evolution@swift.org> wrote:

Hello,

This is a new pitch to allow explicitly specializing generic functions.
Notice that potential ambiguity with initialisers and how I’m currently
trying to avoid it. Please let me know what you think!

David

Allow explicit specialization of generic functions

   - Proposal: SE-XXXX
   <https://github.com/apple/swift-evolution/blob/master/proposals/XXXX-allow-explicit-specialization-generic-functions.md&gt;
   - Author: David Hart <https://github.com/hartbit&gt;, Douglas Gregor
   <https://github.com/DougGregor&gt;
   - Status: TBD
   - Review manager: TBD

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions;
Introduction

This proposal allows bypassing the type inference engine and explicitly
specializing type arguments of generic functions.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions;
Motivation

In Swift, generic type parameters are inferred by the argument or return
value types as follows:

func foo<T>(t: T) { ... }

foo(5) // infers T = Int

There exists certain scenarios when a programmer wants to explicitly
specialize a generic function. Swift does not allow it, so we resort to
giving hints to the inference engine:

let f1 = foo as ((Int) -> Void)let f2: (Int) -> Void = foolet f3 = foo<Int> // error: Cannot explicitly specialize a generic function
func bar<T>() -> T { ... }
let b1 = bar() as Intlet b2: Int = bar()let b3 = bar<Int>() // error: Cannot explicitly specialize a generic function

This behaviour is not very consistent with generic types which allow
specialization:

let array: Array<Int> = Array<Int>(arrayLiteral: 1, 2, 3)

Therefore, this proposal seeks to make the above errors valid
specializations:

let f3 = foo<Int> // explicitly specialized to (Int) -> Void let b3 = bar<Int>() // explicitly specialized to () -> Int

An ambiguous scenario arrises when we wish to specialize initializer
functions:

struct Foo<T: RawRepresentable where T.RawValue == String> {
    let storage: T

    init<U: CustomStringConvertible>(_ value: U) {
        storage = T(rawValue: value.description)!
    }
}
enum Bar: String, CustomStringConvertible {
    case foobar = "foo"

    var description: String {
        return self.rawValue
    }
}
let a = Foo<Bar>(Bar.foobar)

Does this specialization specialize the struct's or the initializer's
generic type? The proposal solves this ambiguity by requiring initializer
generic type specialization to use the init syntax:

let a = Foo<Bar>.init<Bar>(Bar.foobar)

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions
Design

Function calls are fairly straight forward and have their grammar
modified as follows:

*function-call-expression* → *postfix-expression­*
*generic-argument-clause­opt* *parenthesized-expression*

*function-call-expression* → *postfix-expression*
*generic-argument-clause­opt* *­parenthesized-expression­opt*
*­trailing-closure­*

To allow initializers to be called with explicit specialization, we need
to use the Initializer Expression. Its grammar is modified to:

*initializer-expression* → *postfix-expression­* . *­init­*
*generic-argument-clause­opt*

*initializer-expression* → *postfix-expression­* . *­init­*
*generic-argument-clause­opt* ( *­argument-names­* )

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions
on Existing Code

This proposal is purely additive and will have no impact on existing code.

<GitHub - hartbit/swift-evolution at allow-explicit-types-generic-functions
Considered
Not adopting this proposal for Swift.

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

What is the status of this proposal? I recently encountered this issue when writing a DSL for my library. If Swift allows explicit specializing of generic functions, I no longer need a generic type to circumvent this (swift allows explicit specializing of generic objects now).

P.S. the proposal link (https://github.com/apple/swift-evolution/blob/master/proposals/XXXX-allow-explicit-specialization-generic-functions.md) seems dead.

1 Like