[Pitch 2] Light-weight same-type requirement syntax

As stated in the previous pitch, I am in favor of this pitch and SE-0388. I think we should also keep exploring alternate ways of expressing and teaching generics to make it easier to properly teach and understand these concepts.

I believe Swift will become the language that introduces the majority of new programmers to generic programming in a few years. The argument that programmers coming to Swift are already familiar with C#/Java/C++'s take on generics will not hold true in a few years.

Although Swift has a C-like syntax to make it easier to learn, it should not be confined to it. Swift has already drifted from its initial similarity to C-like languages (It dropped C-style for loops, ++ -- operators, added defer, guard, try, etc.). Nowadays, if you look at idiomatic and well-written Swift code, it clearly looks distinct, and in my opinion, much nicer than other C-like languages. It has also deeply affected other C-like languages and they have besome more like Swift. It is good for Swift to lead the way, and I believe these changes are steps in that direction.

I see these changes as more than optional syntactic sugar: I see them as the preferred way to write swift code once they become accepted. The same way T? is a necessary sugar for Optional<T>. As such, I believe a deep revision of the Swift Programming Language book and other tutorials are necessary to bring these syntacic changes into the mainstream of the teaching materials for the language.

However, we should move cautionsly and carefully. We no longer have the luxury of breaking chages that early Swift had. In this regard, I have a question:

Is it possible to cherry-pick these new syntacic changes into an otherwise stable branch and release the corresponding toolchains for people to try?

1 Like

Hello,

The proposal brilliantly explains the advantages of Collection<String> over Collection where Element == String and other syntaxes. If very works well when the primary associated type is constrained to a concrete type such as String.

The proposal does not mention it, but I expect it works as well for subclasses: Collection<Base> would match collections whose elements are Base, or a subclass of Base:

class Base { }
class Child: Base { }
func f<S: Collection<Base>>(_ elements: S) -> S { ... }
let array: [Child] = ...
f(array) // OK

Maybe this feature should be made explicit in the proposal (as expected to work, or as expected NOT to work).

Now, since we're talking about derived types, I see that the proposal does not mention protocol constraints at all. What do you think, @Slava_Pestov, of extending the light-weight same-type requirement syntax to protocol and existentials as well?

Collection<any P> // ?
Collection<some P> // ?
Collection<some Collection> // ?
Collection<some Collection<String>> // ?

I'm not asking for this to enter this proposal, but I think that the Future Directions chapter should say something about those - or clearly say that these are not intended to be supported at all. It is a very natural extension of the current proposal, and I bet users will be drooling for it sooner or later.


EDIT: I don't have any opinion about subclasses and protocols, to be honest. If the intent of the designer is to restrict the light-weight same-type requirement syntax to equality check (==), and not subtyping (:) in Collection where Element == ..., that's OK for me: the heavy syntax is still there when I want to express a complex constraint. Yet, I'm not quite happy that this is not made explicit in the proposal. Maybe such a restriction does create a problem, and an explicit sentence in a proposal would help reviewers.

I've been thinking about this one for quite a while especially since it really looks like what Scala always allowed one to do (which I have a lot of "prior life" experience with).

I made some time to verify if I remember things correctly and if this gives us the same / better / worse expressive power and conciseness, or if there is something to "steal" from Scala 3 which I've not worked with yet, so needed to polish up my knowlage a little bit (3 is fairly recent, with a well proven type system calculus).

Overall:

  • I think this proposal is fine, +1
    • I think Swift actually reads much nicer than the equivalent Scala idioms which is a nice bonus!
  • I would really want to lift the arbitrary "just one primary" restriction; In places where I can see myself using these it immediately is more than one type parameter. So, please, no artificial restrictions :pray:

Minor:

  • I don't really love the naming of "primary" associated type
    • this could be one area to lean on Scala's prior art where those are just called type parameters... the same as on methods/functions. Do we need to draw a distinction here? "Primary" doesn't convey to me what this does or where it appears somehow...

Below my thoughts and double-checking if this is more, less or equally powerful and concise as Scala 2 (and 3) offer, which I consider to be a very powerful typesystem worthy to compare to and steal ideas from whenever we can :wink:


For reference, the equivalent spellings of the proposed here features in Scala (3) look like this:

editable source: Scastie - An interactive playground for Scala.

Notice that what we do in Swift with a nice where clause Scala has to dance around with defining a refinement type with the types being set like T { type A = X } that gets pretty annoying; our where clause is much nicer when the types get long.

So my primary concern to explore was if we're able to consider Require associated type names, e.g. Collection<.Element == String> after all, but after thinking more about it and comparing with Scala where the tradeoff is basically:

  • type parameters, have to specify them all, they are by-order
    • same as the proposal's "primary associated types"
  • via type bindings
    • same as Swift's already existing where clause; where the Where clause is actually much nicer already.

I ended up with the conclusion that this proposal is fine and offers enough conciseness, and when one wants the longer spelling, there's always the where clause...

Overall, +1 and I'm happy to see this -- I really hope we'll allow multiple such type parameters / primary associated types, because a single one is pretty much a show stopped for any interesting types I'd have an interest to use this feature with :slight_smile:

Thank you for the proposal! And I hope you don't mind the bit of Scala here, but I thought it's important to compare a similar powerful typesystem and see how we compare :slight_smile:

7 Likes

Where? I’ve seen this argument several times in this thread but this proposal does not introduce the ability to write the where clause to achieve the same, nor is it already allowed, at least not in positions this sugar is aiming for. It’s this syntax only or nothing!

4 Likes

As has been stated, this proposal is strongly motivated by aligning Collection<Base> with Array<Base>; it would be highly surprising if that didn’t hold with respect to classes.

I think it is worth considering some of the ideas for a general syntax more closely.

The Benefits of the Alternative

These are the benefits (of the alternative syntax), as described in the proposal:

Require associated type names, e.g. Collection<.Element == String>

Explicitly writing associated type names to constrain them in angle brackets has a number of benefits:

  • Doesn’t require any special syntax at the protocol declaration.
  • Explicit associated type names allows constraining only a subset of the associated types.
  • The constraint syntax generalizes for all kinds of constraints e.g. <.Element: SomeProtocol>

I think this wording fails to capture precisely how big of a difference this is. To be clear, the alternative spelling including named associated types would:

  • Work for every protocol ever written in Swift, without any code changes needed by library authors.
  • Allow constraining more associated types
  • Allow more kinds of constraints

In other words, it would be more capable in every conceivable aspect than the syntax being proposed. The proposal itself admits it - it would scale to far more use-cases before you face "the cliff" where the new syntax can't handle your constraints and you have to rewrite everything using where clauses.

IMO, this is a really strong alternative. So I'm looking for really strong arguments against it.

But I'm not seeing them.

Argument 1: Declarations should be self-documenting

This is how the proposal argues against the alternative syntax:

There are also a number of drawbacks to this approach:

  • No visual clues at the protocol declaration about what associated types are useful.

Let's just be clear, here: this is talking about the declaration site; the code in the standard library containing the words public protocol Collection: Sequence { ... } or whatever. And it argues that there should be some loud, prominent notice which says "Hey! This associated type is useful -- and everything else is not!"

Essentially, this is an attempt to make protocol declarations self-documenting, and goes well beyond its remit to improve the syntax of using generics. Personally, I don't think this should be a goal; we have this amazing new documentation engine, and this is precisely what it is designed to do. It gives library authors complete freedom to curate their documentation, and decide how they present important concepts to their users. That's the place where we should communicate which associated types are "useful".

Apple has excellent, professional technical writers to handle this, so perhaps it is easy to forget: users don't learn how to use frameworks like the standard library or SwiftUI by looking at protocol declarations. They learn by reading the documentation.

I've learned from experience that for libraries with any real complexity, even smart, experienced developers will struggle to understand how a library works and how its concepts fit together unless the library's author took the time to produce well-structured documentation. It's hard (it's really hard), but there just isn't any substitute.

Argument 2: Too Verbose

  • The use-site may become onerous. For protocols with only one primary associated type, having to specify the name of it is unnecessarily repetitive.

Firstly, the idea that requiring a label is so terribly verbose that we should limit the expressive abilities of the language seems to be antithetical to Swift's entire design. We don't allow users to omit function argument labels, even if they are considered verbose.

This point also seems in conflict the previous argument - why is it so important to have visual clues at the declaration site, which developers generally don't view too often, but at the same time we should remove visual clues at the usage site, which developers view all the time?

For anything other than the most selective examples of the most basic protocols, unnamed parameters are, in general, not clear. They lose that lovely feature of Swift functions that almost read like prose.
Swift does not optimise brevity over clarity.

So, "Does this proposal fit well with the feel and direction of Swift?"
No, I absolutely don't think it does.

It's hard to find examples of protocols with associated types. They're very limited today, so it seems like a lot of frameworks try to avoid them. But here is one example, from Foundation:

// Developers define new attributes by implementing AttributeKey.
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
public protocol AttributedStringKey {
    associatedtype Value : Hashable
    static var name : String { get }
}

This seems like a good candidate for a "primary associated type", doesn't it? Well, let's see how it looks in practice:

func tag(
  _ string: inout AttributedString,
  with: some AttributedStringKey<Int>
                                 ^^^ - huh? is this the key type?
)

func tag(
  _ string: inout AttributedString,
  with: some AttributedStringKey<.Value == Int>
                                 ^^^^^^^^^^^^^^ - Much clearer
)

The version with labels also reads much better; it accepts "some AttributedString key whose value has type Int". It has that fantastic clarity that you get from Swift functions - and I think that's far more important when we consider how easy the language is to learn, and how approachable it is for newcomers.

How about other popular protocols, like Identifiable? It also seems like a good candidate. This is what the actual declaration looks like - actually, very well documented IMO. No need for extra visual clues here (although of course, it will need to add them regardless to get the new syntax).

@available(SwiftStdlib 5.1, *)
public protocol Identifiable {

  /// A type representing the stable identity of the entity associated with
  /// an instance.
  associatedtype ID: Hashable

  /// The stable identity of the entity associated with this instance.
  var id: ID { get }
}

So how does this look if we make ID a primary associated type?

func updateItems(_ items: [some Identifiable<Int>]) { ... }
                                             ^^^
                                  Huh? Are the items Ints?
                                Are these "identifiable Int"s?

func updateItems(_ items: [some Identifiable<.ID == Int>]) { ... }
                                             ^^^^^^^^^^
                                       Oh. That is just totally clear.

And again, it reads much better. This function accepts an array of items; the type of those items conforms to Identifiable, and its ID is of type Int. They are not "identifiable Int"s, and that distinction is made clear with very little extra syntax. I would not call this "verbose".

Let's also look at some more advanced examples. What does it look like to express a 2D collection? Let's pretend we adopt the extension to allow some X to express a subtype constraint:

func test(_: some Collection<Int>) // The most basic use-case.
func test(_: some Collection<some Collection<Int>>) // Nested angle brackets.
func test(_: some Collection<some Collection<some Hashable>>) // some some you what?

The syntax quickly breaks down in to a mess of nested some types and, crucially, nested angle brackets. Remember "angle bracket blindness"? Meanwhile, if we have the ability to constrain named associated types, we can avoid a lot of that:

func test(_: some Collection<.Element = Int>) // The most basic
func test(_: some Collection<.Element: Collection, .Element.Element = Int>) // No nesting. No repeat 'some's.
func test(_: some Collection<.Element: Collection, .Element.Element: Hashable>) // And it scales.

Now, this is still a complex generic signature - a collection of collections of hashable elements - but I think the second version is easier to read because I don't need to track the hierarchy in my mind to know how deep I am in the signature; there is no nesting. And the noise of all the some keywords is greatly reduced.

And if this syntax was extended to more than one associated type, I think the parameter labels become even more important. This is an example adapted from Doug's recent proposal:

func test(_: some DictionaryProtocol<some Hashable & Codable, Pair<some Codable, some Codable>>

// vs

func test(_: some DictionaryProtocol<.Key: Hashable & Codable, .Value = Pair<some Codable, some Codable>>
                                     ^^^^^                     ^^^^^^^^

I think separating the Key and Value types here adds a lot of value. It is just much clearer at the point of use, like Swift function calls are. It just looks like Swift. It just fits, I think.

No, it is not the shortest, tersest possible syntax - but Swift just isn't that language. We don't optimise for brevity over clarity; we do the opposite. At least, that's how I've always understood the language.

And I think in all of the cases I've shown, those labels do actually add value. Let's not dismiss them out of hand.

Argument 3: Not a big improvement

  • This more verbose syntax is not as clear of an improvement over the existing syntax today, because most of the where clause is still explicitly written. This may also encourage users to specify most or all generic constraints in angle brackets at the front of a generic signature instead of in the where clause, which goes against SE-0081.

Because this is already so long, I'll refer to a previous post on this issue.

In short: there is still great value, because we bring the constraints much closer to the thing they apply to. Currently, generic signatures are chopped up with pieces at the start, middle, and end of the function signature. Consolidating them has value.

As for SE-0081? This proposal also goes against SE-0081! It's been 6 years since that proposal was accepted, and we can certainly use that experience to opt for a different direction. I don't buy the idea that we should be constrained by SE-0081.

Idea for how to proceed

IMO, we should proceed by implementing the syntax with named constraints. As we've seen, it has a lot of benefits, it is clear, and it is scalable.

We can then, as a future extension, discuss allowing unnamed parameters in to that list. The way I see it, it would be like a proposal to omit function labels if the function has one parameter:

// This is the analogy:
func doSomething(with: Int) { ... }
doSomething(42)
            ^^ - removes the 'with'

func doSomething(with: some Collection<.Element == Int>)
func doSomething(with: some Collection<Int>)
                                       ^^^ - removes the '.Element =='

Personally, at this stage, I don't think I would be in favour of that, but I think it is a separate discussion we should have, with costs and benefits that we can evaluate separately.

40 Likes

The biggest argument against it is that it doesn’t achieve the intent of the proposal: unifying the syntax of Array<Int> and Collection<Int>.

1 Like

I'm very glad to see you still want to participate in Swift evolution :wink:

7 Likes

For me, this isn‘t even something I‘d consider desirable (same syntax for two different things).
Also, I‘d add to Karls list that a primary-annotation is yet another variant developers have to learn.

I can‘t emphasise enough how weak this argument is (imo): Even for the Collection example, you can come up with cases where it is actually the index which matters. There is no clear separation between important and unimportant types, and it is very natural to use the order to express which types are considered „more primary“ than others.

11 Likes

Given that we're looking towards uniting some syntax for protocols and generics, I don't think it's out of scope to consider how this feature might interact with or be influenced by other generics features that are on the horizon, namely, variadic generics.

One of the things that came up in the VG thread was the restrictions that generic types are allowed only a single variadic parameter because of the ambiguity caused the lack of generic argument labels:

struct S<Ts..., Us...> {}
// which are Ts, which are Us?
let s = S<Int, String, Float>()

IMO that's a restriction we will inevitably want to lift, and what feels to me like the natural syntax in that case is simply to... allow labels for generic arguments:

S<Ts: Int, Us: String, Float>()

The S<.Ts == Int, .Us == String, Float> syntax doesn't make much sense for generic types, IMO—generic parameters aren't members of the generic type, and we'd be cribbing == from same type constraints in where clauses, which also feels wrong to me.

OTOH, I think the 'natural' syntax for generic types also makes sense for protocols. Collection<Element: Int> reads just fine to me, and is marginally terser than Collection<.Element == Int>.

That said, even if we already had generic argument labels today, I think there would be merit to this proposal. For some protocols like Collection, constraints on the Element type are just so much more common than any other constraints, and the point-of-use so clear that IMO there are clear readability wins for the Collection<Element> syntax. It's important for the API author to be in control here—as @Karl notes, there are plenty of protocols (e.g. Identifiable) where simply allowing anonymous specification of any associated types at all would hurt more than it helped.

That said...

this analogy is only true if we drop the requirement of this proposal that protocol authors specify exactly which associated types may be specified anonymously. As written, this proposal is more analogous to allowing function authors to choose which arguments require a label when the function is called, which, of course, we already allow and make extensive use of. :slightly_smiling_face:


Most importantly, though, I don't see a good reason why either the "named associated type constraints" or "anonymous associated type constraints" design directions have to preclude the other. It seems like it should be perfectly possible to introduce either one after the other, so it's not as though adopting the Collection<Int> syntax would prevent us from later allowing Collection<Element: Int, Index: Int>.

11 Likes

I agree that labels for generic parameters would be a nice addition to the language.

One generic type is generally fine (Array<Int>), two generic types can be okay if you are familiar with the type already (Dictionary<String, Int>), but three or more generic types start becoming an incomprehensible soup (Foo<String, Int, String>).

They remind me of C functions: foo(42, -7, true). Swift improves on those by adding labels, and it makes just as much sense for generic types as it does for functions IMO.

We can bikeshed the syntax after we agree on the direction, IMO. "<Element: Int>" has things going for it; I've also suggested that the compiler could just DWIM when users mistakenly constrain to a concrete type. There are other possibilities and considerations, though.

Swift does favour putting API authors in control. If we already had the general constraint syntax, you could imagine allowing API authors to define aliases for common constraints, as they see fit.

This could involve minimal code changes, unlike the highly invasive changes proposed here.

#if swift(>= X)
  typealias Collection<T> = Collection<.Element == T>
#endif

// Can totally live alongside the regular Collection protocol.
protocol Collection: Sequence { ... }

But again, I think this is a very separable feature and warrants its own discussion.

I've already outlined the issues as I see them in previous posts in this thread:

  1. This is an opt-in, highly invasive feature for library developers.
  2. You don't get any constrained opaque types unless you opt-in and make those very invasive changes.
  3. This is a more controversial shorthand (IMO), and should be evaluated in isolation.

This proposal includes an entirely new way of declaring protocols, not just using them, while at the same time being very restrictive, not scalable, and going against an important Swift philosophy that we should favour clarity over brevity at the point of use. It's the wrong place to start.

I think the most logical way to approach this is to implement the general feature and syntax (Slava says it's already somewhat implemented behind flags - great!), and discuss this limited shorthand later. Bundling the shorthand together with the slam-dunk, very, very desirable feature of constraints on opaque types, is just poor practice.

8 Likes

I fully agree with the actual point, that Identifiable<Int> does not feel natural in the same way that Collection<Int> does. It’s also true that:

…but I think it would be disingenuous to argue that this is anywhere near as common as wanting to constrain the element type, or that Collection<Int> // indexed by int makes exactly as much sense as Collection<Int> // contains ints.

Leaving this as a purely intuitive distinction is uncomfortable for the kind of person who spends their weekend litigating language evolution proposals, but I think there’s a clear formal distinction hiding in plain sight: every case I have seen (or found in our own code) where a primary/anonymous associated type feels right has been a monad (or at least a functor). Or, in more Swifty terms, a type that admits a reasonable definition of flatMap (or at least map) over the primary type.

It’s no coincidence that map and flatMap are the canonical examples of Swift functions where it’s okay to use T as a type parameter: monads and functors don’t impose meaning on their subject types and labelling them doesn’t add much value.

Given this, I would like us to get to a place where both labelled and unlabelled constraints can be attached directly to the type, with a recommendation that unlabelled ones are used for “container-like or publisher-like types”, or some such euphemism. I don’t particularly care which order we do it in.

(As far as I’m concerned SE-0081 is actively harmful and going against it is highly desirable.)

6 Likes

I don't think that point is being made; just that if you do want to add an additional constraint, you need to totally rewrite your function signature. There is a biiiiiig cliff.

func doSomething(_ items: some Collection<Int>)

// Oh no! We need Index to conform to Hashable!
// It's just a simple matter of... oh...

func doSomething<C>(_ items: C) where C: Collection, C.Element == Int, C.Index: Hashable

That's not good for usability or approachability. You can do the very specific things the proposal wants you to do, but as soon as you experiment a little bit, it kicks you in to the wilderness.

7 Likes

Hmm. Did you delete a bit about IdentifiedBy<Int> working better with the proposed syntax? I think that’s true, and it also somehow waggles its eyebrows at the insight that you could map over it if you really wanted to. I feel that this somehow simultaneously undermines and reinforces my point about m-words.

Yeah, I have a tendency to write too much so I'm trying to limit myself to single points wherever possible :sweat_smile:

But sure, if we had this feature already, there's an argument that Identifiable would actually be called IdentifiableBy<ID>. Because:

  1. Users will want the ability to use constrained opaque types
  2. You can only get constrained opaque types using this syntax
  3. This syntax doesn't work well for Identifiable<ID>. You kind of need the word "By" for it to work.

Library authors design their libraries for the language we have, so I think the order definitely does matter. If we do the shorthand and no general syntax, it biases library design and authors will try to make things fit. There will be a lot of pressure from their users to make this work.

If we did shorthands via typealiases (or some other kind of alias), there would be more room for library authors to make those kinds of decisions. Collections, for sure, are often constrained by their element type, so the library author might decide that CollectionOf<Element> is a better shorthand than just Collection<Element>, the same way they might decide to add a variants of a function with slightly different labels.

If the idea is that the library author should be allowed to decide, then we should let the library author actually decide.

3 Likes

We know why we are discussing light-weight syntax: the full syntax is too complex for many users (all credits due to @hborla).

But I agree with @Karl: this proposal as well [SE-0341 Opaque Parameters] both simplify the generics syntax for a few privileged cases. Anything that goes beyond need to switch to the "full" generics syntax, and there lies the "cliff".

This cliff creates two different problems:

  1. Developers who aren't familiar with generics (the targets of the simplified syntaxes). They don't even know where to start from. The full syntax is not derived from the simplified syntax.
  2. Developers who are familiar with generics. They feel fatigue when they know they have to rewrite their function signature (or protocol declaration). It is boring, and it is possible to make a mistake.

Very quickly speaking, and not wanting to pretend I know what's inside their minds, it looks like @Karl is talking about problem 2, when @hborla may like to address problem 1.

May I suggest that both problems could be helped with an automatic refactoring tool? Xcode already provides some of them (Refactor > Rename…, Refactor > Generate Memberwise Initializer…, etc.) Why not add a "Refactor > Convert to Generic Signature" refactoring tool?

-func doSomething(_ items: some Collection<Int>)
// Shazam!
+func doSomething<C>(_ items: C) where C: Collection, C.Element == Int

Now Karl can easily extend the refactored declaration with the C.Index: Hashable requirement :+1:

And developers who aren't familiar with generics are guided on their way to becoming an expert :+1:

3 Likes
Meta comment: Swift Evolution and the Conway's law

Swift Evolution proposals almost never extend to the tooling areas, or documentation.

For a recent example, my comment about SE-0340 and documentation was totally ignored. Of course, I do not claim that my humble contributions all deserve an answer.

Yet I think Swift Evolution is an exemplar case of Conway's Law. The Swift Evolution process is isolated from documentation and tooling, and it shows.

I can understand that our proposals can not make requirements on documentation and tooling. Even the Core Team can't.

But does it imply that we have such important blind spots? Documentation and tooling should be able to enter our language discussions. We should actually be able to leverage documentation and tooling towards our goals.

7 Likes

Yeah, if we're introducing the shorthand now, I definitely think we'll need to think about how we might design various aspects if we already had the long-hand Collection<.Element == Int> (or Collection<Element: Int> syntax. Your typealias suggestion here seems reasonable, so it would be kind of a bummer if protocol authors had to do something like:

#if swift(>= X)
  typealias Collection<T> = Collection<.Element == T>
  protocol Collection: Sequence {
#elseif swift(>= X-1)
  protocol Collection<Element>: Sequence {
#else
  protocol Collection: Sequence {
#endif

So to the extent that our preferred syntax for enabling the shorthand may depend on a future mechanism for more complex constraints in angle brackets, I agree we have a bit of a logical dependency that we'll want to consider carefully. But other that that I only meant to point out that I don't see this proposal as cutting off future evolution for the Collection<.Element == Int> syntax or similar.

I'm of the mind that we would still want this shorthand even if we had the longer angle-bracket syntax, but I pretty much agree with you that having just the shorthand leaves the language in a bit of an awkward place, so I would hope that the general feature comes quickly after. IMO the benefits to the end user would be worth it, but I do worry that we'd create a bit of an attractive nuisance that library authors would reach for in ill-advised manners simply because the general feature is still lacking.

For instance, will authors of protocols like Identifiable update ID to be a primary associated type in Swift N (simply because they want to let users avoid where clauses and there's no other way to do that) enabling the sub-optimal Identifiable<Int> syntax, only for users to be able to specify Identifiable<ID: Int> in Swift N+1?

Taking this analogy to it's logical end would allow the API author to specify a generic argument label in addition to a generic parameter name:

protocol Collection<Of Element, IndexedBy Index: Strideable> where ... { ... }
...
func doSomething(with collection: Collection<Of: Int>

which I... actually might like?

5 Likes

I don't think so; as far as I'm concerned, I'm (intending) to talk about both.

Whenever I learn a new programming language or concept, I start with basic examples, then I play around with them and add things, applying the rules until I develop an intuitive understanding of how things work. I think that's a very general thing.

The problem with this design is that you can't do that. As I've shown - want to add some collection indexes to a Dictionary or Set? Then you start needing to rewrite the function signature in a dramatic way and everything starts to fall apart.

Not only does that not help, I actually think it hurts. It makes you feel like adding a Index: Hashable requirement is some super-advanced concept that should only be attempted by rocket scientists. It's so painful that it acts as a disincentive to learn about other things.

--

Also, one thing I find a bit irritating: it isn't just Holly who cares that generics are approachable and learnable - I've also been mentioning it and offering ideas for years.

For example, back when the idea was that we needed "partial protocols" or "existential self-conformance" to make our generics system flow better, I set out what I thought the issues were.

  • That existential syntax was too simple, and too easy to confuse with generics
  • That the compiler would automatically box types in to existentials, but not automatically open them

And that these combined to make generics difficult to understand.

Maybe others had mentioned some of these issues before, but I've been active on the forums since they started, and I'd never seen them expressed in that way in that context. Those were original thoughts, and I took the time to write them up, and the risk to express them in an open forum. The reactions at the time seemed to suggest they were not obvious comments.

Now those things are coming in to the language - which is great! I'm not acknowledged as contributing towards any of those features, of course.

I don't participate in these forums to chase acknowledgement and I would never ask for it, but in the context of your post and the points that you're making, I would like to draw attention to the fact that I do very much care about making generics learnable. I'm not only interested in advanced use-cases.

But that's all I'd like to say on that subject. Again, I don't ask for acknowledgement and find it a bit embarrassing to even talk about it.

4 Likes

My "not wanting to pretend I know what's inside their minds" precaution wasn't enough, so please accept my apology for the artificial distinction between you and Holly I made in order to support my argument. It was probably tactless. Still, I think it was useful to split the "cliff" problem into two sub-problems.

3 Likes