Many of the objections are about the idea of implicitly declaring generic type parameters via stored properties:
which I reiterate is an idea that sits poorly with me as well, although I do think there's some more exploration I'd like to do on the topic. I already put a question mark on that section title but I'll add something else to make clear that that's not what's actually being proposed here and that we shouldn't get caught up on it. For now, we all agree: thank goodness Swift doesn't currently work like that...
What about the more important main pieces of the idea?
func receive (_ input: <Input>)
func feed (_ food: Recipient.Food, to recipient: <Recipient: Eater>)
var anyKindOfSevenYouWant: <T: ExpressibleByIntegerLiteral> {
.init(integerLiteral: 7)
}
let boxedUpValue: some Equatable = ...
let value: <Value> = boxedUpValue
if let dynamicallyCasted = someOtherValue as? Value {
print(value == dynamicallyCasted)
}
It just seems beneficial to have a single place where the generic type is defined, rather than finding it someplace you weren't really expecting. Finding out that all of these parameters are not actually Swift.Int, but are actually the generic type once you get to the last parameter does not seem good.
Is this basically a way to get a typealias to that specific some Equatable?
I really like this part of your pitch. I agree with others that introducing generics into structs and other types this way feels pretty weird, but I like it as a convenient way to make functions and subscripts generic.
Some people may not prefer them, but I actually like that it keeps the angle brackets. It maintains parity with other generic code: the traditional generic function syntax, generic types like Result, etc. I think your syntax improves greatly on the legibility without introducing new visual cues (like any or some) which is great.
It has been suggested in another thread that some should maybe be reserved only for opaque return types and not used as a convenient way to introduce anonymous generics.
I am inclined to agree. Opaque return types are confusing enough at first. Overloading the same keyword to also create ânormalâ generics just muddies the water more, I think.
A quick justification
My rationale for not overloading the meaning of some goes like this:
Suppose Iâm new to learning generics. I learn about the traditional syntax first, for whatever reason. So I write some functions like this:
I later learn that âsome can be used to introduce anonymous generics,â i.e. itâs just some shorthand syntax. I like that so I refactor my functions as such:
func send(input: some Numeric)
func receive() -> some Numeric
But now, Iâve just changed the meaning of my receive function, because some has a different meaning when used with a return type. It makes the type âopaqueâ not âgenericâ as my original function was written.
The explanation of some would need to be much more nuanced because itâs interpretation depends on how youâre using it.
Furthermore it would be pretty unfortunate that in order to have a generic return type I would have to keep the traditional generic syntax and canât use the more convenient some keyword.
Thatâs why I like your design. I can just cut and paste the generic signature on the end of the function signature.
So, if it is too problematic to overload some to mean âanonymous genericâ then I think your proposed syntax here is a good alternative.
I think the concern about a âsneakyâ generic appearing at the end of a long declaration like this is a very warranted. Especially upon seeing the example Syre provided!
To alleviate this, we could require that a generic type must appear lexically before other points of use. So according to that rule, this example you gave would have to change:
// error: âP.Mealâ cannot appear before generic âPâ is introduced.
func feed(food: P.Meal, to person: <P: Person>) -> P.Thanks
// A Fix-it could move <P> to the traditional position for generics.
func feed<P: Person>(food: P.Meal, to person: P) -> P.Thanks
I think this rule is pretty easy to justify and explain.
Finding new syntax for protocol existentials might be important to consider here. There have been lots of different proposals, like any Collection and any Collection<.Element == Int> and so on.
The core of your pitch only addresses the ergonomics and legibility of generic function declarations, but doesnât really help the existential syntax situation. Maybe thatâs fine though â existentials are a related but still pretty different language feature.
I donât think what youâre proposing would prohibit future efforts to improve the syntax and expressiveness of protocol existentials.
I don't care for this, as it would be too easy to accidentally remove the <T> part when refactoring or renaming a function or data type and then getting a compile error because you've no longer defined it anywhere. By restricting the generic definition to just after the function or data type name like we have now, there is not that same danger.
I want to be able to see any generic placeholder up front rather than have to scan the function or data type to figure them out.
I prefer the parallelism between seeing a property's type as Thing<Int> and its type definition as struct Thing<Value>, for instance. Just seeing the type as struct Thing with the <Value> part tucked away somewhere inside adds unnecessary friction, IMO.