Currently, functions cannot explicitly declare generic parameters, for example:
func foo<T>() -> T.Type {
return T.self
}
var type = foo<Int>()
| `- note: while parsing this '<' as a type parameter bracket
`- error: cannot explicitly specialize a generic function
if you want to realize the function foo need to be implemented in a different way:
class MyClass<T>{
var type = T.self
}
struct MyStruct<T> {
var type = T.self
}
var x = MyClass<Int>().type
print(x)
var y = MyStruct<String>().type
print(y)
So why can't we just support specifying specific generalizations?
Then there is the case when the type derivation is not the type the user wants:
func foo2<T>(value: T) -> T.Type {
return T.self
}
var z = foo2(value: 3)
print(z) //z is Int.Type
But I have to write it like this here if I want 3 to be of type Int64:
var z = foo2(value: 3 as Int64)
print(z) //z is Int64.Type
Proposed solution
So it might be better to explicitly declare the type of the generalization, or to make the language easier to use, as in the example above:
var type = foo<Int>()
var z = foo2<Int64>(value: 3)
print(z) //z is Int64.Type
There's no technical reason that specifying the generic arguments of a function this way isn't supported, and as you've observed the syntax even parses. I think it was more of a stylistic choice, but I don't actually know the history behind this decision.
Yes, I think the type is going to be declared anyway, so I might as well have it in the right place instead of using var x: someType = foo() or var x = foo() as someType
This seems strange, like the return value of some statement determines the result of a function or a type-forcing conversion.
I think var x = foo<someType>() is the most intuitive and most elegant.
It seems like the view eight years ago was that the feature seems entirely reasonable. Are you looking for contrary views that it should not be implemented?
This is a common misunderstanding: the as operator does not perform type conversion here: the two statements are completely equivalent.
Your point goes to the crux of the issue here, which @Douglas_Gregor brought up in his reply from many years ago. By not having this feature, we are deliberately requiring that a generic function can only be used if all of its parameters can be specified entirely through a normal parameter or the return type.
My opinion hasn't shifted since my response in 2016. I still like the restriction that each generic parameter must be mentioned within the function signature, because it (still) pushes us toward better APIs. I still think the explicit specialization syntax is reasonable, but haven't felt much need for it in the intervening years.
For me, the only difference since 2016 is that we now support C++ interoperability, so there are C++ function templates that don't follow these rules and can't meaningfully be used from Swift or have more awkward spellings in Swift than in C++.
Since 2016 we've also seen the growth of APIs which take the type parameter explicitly but defaulted for convenience, e.g.
func foo<T>(_: T.Type = T.self) -> T.Type
which affords callsites like:
let type = foo(Int.self)
let defaulted: Int.Type = foo()
For example, withTaskGroup(of:returning:isolation:body:) offers an explicit-but-defaulted argument for its returnType. The return type can always be explicitly spelled out in a full closure signature if it needs to be disambiguated—but it can less verbose to pass it directly.
We also have examples like AsyncStream.makeStream(of:bufferingPolicy:), whose defaulted elementType argument supports alternate spellings, making both of the following valid:
let (s1, c1) = AsyncStream<Int>.makeStream()
let (s2, c2) = AsyncStream.makeStream(of: Int.self)
I also think the restriction that every generic parameter must be mentioned in the function signature is fine, and the example I provided does not break that restriction.
I just want to provide a more intuitive syntax that can pass generic types directly to functions, which I think would make the code more readable.Instead of weirdly declaring the type of a variable or converting it with as.
I'm not sure how that example changes the discussion since 2016.
In the line let defaulted: Int = foo(), the type T = Int is also still there in the return type part of the function signature, otherwise the compiler wouldn't know which type the default argument value _: T.Type = T.self should be.
I understand that no conversion is done here, but as looks like some kind of conversion is done, so that's exactly what I'm trying to say, that I'd like to have a more intuitive and readable syntax for passing generic parameters to functions, rather than using as or something else.
So passing a generic type to a function doesn't break the restriction that the function's generic type must be in the function's signature, it just provides a better and more intuitive syntax to avoid using as.
It changes things because at least as often as not the type can be inferred from context, while still giving a handy way to specify it when needed:
func getOne<T>(_ type: T.Type = T.self) -> T { ... }
func collectSome<T>(_ count: Int) -> [T] {
(0..<count).map { _ in getOne() } // no type specification needed
}
Yes, a type still has to be specified somewhere but that's true no matter what; these default type parameters allow you to get rid of the superfluous type annotations.
There's also the problem that you can't use metatypes in embedded Swift. This rules out a lot of APIs for Unsafe(Buffer)Pointers, including withUnsafeTemporaryAllocation(of:capacity:_:) and all of the load/store operations on Unsafe(Mutable)RawBufferPointer. Which is odd, because I would have thought embedded would be more likely to need these things.
Try out the latest nightly toolchain, those functions should work as you expect them to in embedded swift. (You may run into missing adoption of typed throws)
In the integer generic parameters review thread, Rauhul raised the question of how integer generic parameters might be derived from value parameters. Unlike types, there are (at least in the proposal as it stands) no "meta-integer" types unique to every integer value, so an integer
parameter with a default argument can't naturally serve as a constraint on a generic parameter the way a metatype parameter can:
func foo<let length: Int>(of len: Int = length) -> Foo<length>
// `length` is just the default argument to `len`, so nothing prevents
// it from being something else:
let myFoo: Foo<17> = foo(of: 38)
Strictly speaking, this is also a potential problem for class metatypes, since you could pass a subclass as the value of a base class metatype, and our implementations aren't very principled or consistent in what they do if the value of the generic argument and metatype value argument diverge:
func bar<T>(as _: T.Type = T.self) -> Bar<T>
class Base {}
class Derived: Base {}
// This is a valid call. It statically produces a Bar<Base>, but could
// do something different dynamically with `Derived.self`
let myBar: Bar<Base> = bar(as: Derived.self)
If we're confident that this style of API design is what we want, would it be worth considering a more direct way to declare function parameters as being strictly generic parameters, and not value parameters that happen to indirectly act as deduction guides for generic parameters?