A new idea about generics

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)
}
1 Like

I think these examples still have the same problems mentioned above.

func foo(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int, g: Int, h: Int, i: Int, j: <Int>)

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:

func send<Input: Numeric>(input: Input) 
func receive<Output: Numeric>() -> Output

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.

Cheers! Thanks for reading

3 Likes

Oh, also, it might be helpful to consider more complex uses of this syntax. How does it feel?

E.g.

func what(_: (<Arg>) -> <Output>) -> (Arg) -> Output

func hmmm(_: [<Key>: <Value>]) -> [(Key, Value)]

func okay(_: KeyPath<WrappedValue, <Pub: Publisher>>) -> Pub.Output

func sure(_: Result<<V>?, <E>>) -> V?

These aren’t really meant to be legit methods, I’m just trying to mash a weird combination of types together so we can get a feel it.

2 Likes

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.

4 Likes

Is there any chance we will see first class generic function types in Swift any day?

func processMiscCollections(extremum: <C: Collection> (C) -> () -> C.Element? where C.Element: Comparable) {
    …
}

processMiscCollections(Collection.min)
processMiscCollections(Collection.max)

Current proposal conflicts with what seems to be the most natural syntax for that.

2 Likes