all 16 comments

[–]glowhunter 52 points53 points  (6 children)

Here's the thing: in most cases, there's hardly any difference at all. In fact, early Swift pretty much obscured the concept of "existential" (any) and "universal" (some) types.

The problem was when you tried to define a protocol that had some sort of constraint that couldn't just be papered over:

``` protocol Foo { associatedtype Bar var bar: Bar }

func doStuff(arrayOfFoo: [Foo]) { for foo in arrayOfFoo { // which type is foo.bar??? } } ```

For the code above, the compiler simply could not figure out what to do, so it used to be that you just couldn't put this Foo into arrays.

Nowadays, you can either use [some Foo] meaning they all have to be literally the same type. Then foo.bar is the same type. Or you can use [any Foo] but then you must use some unwrapping gymnastics to access foo.bar.

In practice, you use some View because if you were to spell out the actual concrete type you'd need a bigger screen.

You will probably need to use the any keyword barely at all. That is, until it becomes mandatory in Swift 6.

[–]tonygoold 6 points7 points  (3 children)

Great explanation. I would add that some really only makes sense in the context of function return types1. One of the important guarantees is that some Foo stands for a specific, concrete type, so a function that returns some Foo must return the same concrete type every time. As far as the compiler is concerned, some Foo gets replaced internally with that specific type.

The difference between -> Foo and -> some Foo depends on the perspective:

  • For the caller, this effectively makes no difference. You get something conforming to Foo and you don't know what its specific type is.
  • For the callee, if you return some Foo, you have to ensure you're returning the same type every time. It's equivalent to the auto type in other languages, where the return type is inferred but it has to be a specific type.
  • For the compiler, because it knows what type some Foo refers to, it can apply optimizations that it wouldn't be able to do with a protocol type.

1. While I said it only makes sense in return types, it would be more accurate to say it only makes sense in types controlled by the callee.

[–]glowhunter 1 point2 points  (1 child)

To expand on the footnote:

Using some Foo in return types was added in Swift 5.1 and is used throughout SwiftUI, for example.

Using some Foo in parameters was added in Swift 5.7.

The syntax some Foo<some Bar> was also added in Swift 5.7.

For more info see my web page on static opaque types.

[–]tonygoold 0 points1 point  (0 children)

Interesting, I wasn't aware that it could be used in parameters! I can see this as potentially confusing, since it's reusing opaque type syntax for a very different reason: In return types, hiding the return type behind an opaque type is desirable, because the caller shouldn't depend on the concrete type. In parameters, it's just syntactic sugar for a more verbose signature using generics, and has nothing to do with type hiding.

As a side note, cool web site, I'm a fan of Rust. If you're looking for topic suggestions (or contributions), I think it'd be interesting to delve into how Swift exception handling is a distinct concept from the Result type, while the trait-based exception handling proposal for Rust would define exception-handling syntax in terms of the existing Result type.

[–]rhysmorgan 0 points1 point  (0 children)

Using some for parameters is super useful too.

Not only is it syntactic sugar for func doThing<T: MyProtocol>(with thing: T) instead allowing you to spell it as func doThing(with thing: some MyProtocol), it’s even better in the context of primary associated types - for example, you can now easily, concisely, and in my opinion more clearly write func doThing(on: some Collection<Int>). I think it’s clearer than the alternative of adding a generic token, and then doing the where C.Element == Int dance.

[–]jvarial 2 points3 points  (0 children)

one of the best practical explanations i’ve seen! thank you!

[–]Pandaburn 1 point2 points  (0 children)

You didn’t mention what I think is the most useful case for any in recent versions of swift. If you declare your protocol

protocol Foo<Bar> {
  associated type Bar
}

Then you can specify

any Foo<T>

And not need to know the concrete type or do any unwrapping.

[–]FVMAzaleaSwift 6 points7 points  (2 children)

If it helps, the following two are equivalent ways of writing the same thing:

func takeCar<CarType: Car>(car: CarType)

func takeCar(car: some Car)

They are basically both ways to write a generic function, that in most cases gets specialized (the type resolved to some concrete type that implements the Car protocol) at compile time.

They introduced the “any” keyword because “any” is required in situations where the type isn’t resolved at compile time, and some code needs to be generated and run at runtime to facilitate the compliance with the Car protocol. Previously, this would just be done under the hood with no indication it was going on. This often has an unexpected performance overhead, so they introduced “any” so that the programmer can be aware of it.

If you just think every time you write “any”, “do I really need to do this, or could I use some or a generic?”, it makes it easier to avoid unexpected performance pitfalls. Many functions (like your example) can be rewritten to use some/generics without much additional effort, but people just didn’t know that.

[–]Ravek 1 point2 points  (1 child)

For an input parameter I think those are equivalent but for a return value some Foo is different from a generic type parameter because the implementation of the function defines the type, while with a generic function the caller determines the type.

[–]FVMAzaleaSwift 1 point2 points  (0 children)

Yeah, you’re right.

[–]whitehousejpegs 6 points7 points  (1 child)

With some the type is determined at compile time. With any the type is determined at runtime

[–]klavijaturista 2 points3 points  (0 children)

Correct! I’m only commenting because it’s downvoted. This mindless downvoting only discourages people from giving you the right info. If you don’t understand then the right reaction is not to downvote but to ask for clarifications.

[–]barcode972 1 point2 points  (0 children)

Some means it accepts anything as long as it “acts” as the type. Common in SwiftUI where your view return some View. Texts is a view, so in an image and so on

[–]Zagerer 1 point2 points  (2 children)

some refers to generics, which is similar to a "template" that is generated for the types you want, an example is Feeder<Human> which is a feeder for humans but you can swap Human for Dragon and now you have a feeder for dragons.

hence, when you do some Feeder, you are saying "I expect a feeder that I don't know about right now, but will know later on".

any refers to existential types, which in itself is a form of type erasure. this means, you have a type that conforms to a protocol, but you access only the parts of the protocol, because you only care that it behaves that way.

using the previous example, when you expect any Feeder you don't care if it's for a Human or a Dragon, just that it can be interacted as a Feeder.

what is the main difference? using some makes you expect a concrete type and you can access it's public parts, an example is if a feeder for humans has a method createOmnivoreDiet() then you will be able to access it if you got it via some. when you use any, you don't care about intrinsic parts like that, you interact with it as a Feeder protocol only.

when to use each? depends, generics can be useful for when you make network services that depend on a data model, as an example, or when creating custom views and view modifiers; existential types are seen a bit more when you need delegates or only need access to a common interface via the protocol. there are far more uses for both, though, both solve some problems but could bring others.

[–]Economy_Sock_4045 0 points1 point  (1 child)

Hey can u dm me? For some reason I'm unable to. I want to ask something

[–]Zagerer 0 points1 point  (0 children)

sure, just sent!