Confusing "some" and unnecessary @ expression

Suppose you have a function that is a factory e.g. it can return any subclass of View.
According to a blog here: Some keyword in Swift: Opaque types explained with code examples
there is an expression "@ViewBuilder" which overcomes the inability of "some View" to allow multiple returns of different types:

@ViewBuilder
func makeFooterView(isPro: Bool) -> some View {

Why require this terribly awkward syntax? Why create yet another obscure "@" expression? Would it not be simpler to call the function what it is, a factory?
e.g.

   factory func maker(_ parameter:String) -> View {
    switch (parameter) { // etc.
    }
   }

Imagine that you create another result builder that returns Views. How would Swift know which "factory" (result builder) to use — yours or ViewBuilder? — to do the work to return the view?

1 Like

I think the answer is quite easy: you can fully qualify the return type (SwiftUI.View) to disambiguate.

What if yours also returns SwiftUI.Views?

A @ViewBuilder is just a type of @resultBuilder. You must specify what kind of @resultBuilder you are using to build the some View.

Was trying the socratic approach to help them arrive at this point :sweat_smile: But yeah

As a somewhat less unrealistic example, there’s probably several result builders out there that use some collection type, such as an array. If you have a factory function that returns Collection, how is the compiler supposed to know which you mean?

1 Like

The way I see it, this is just some sugar to avoid having to spell the view builder explicitly. It'd have to be derived from the View protocol. In the end, the desugared version could look like this:

@SwiftUI.View.ViewBuilder
func maker(_ parameter:String) -> some SwiftUI.View {

Of course, since SwiftUI.View is a protocol and you can't nest a type within it (View.ViewBuilder), it'd probably require some other language feature to create this association.

I think the proposed syntax is awkward, and it doesn't save you much over the already existing one, but technically it could be made to work by having the ability to associate a default result builder to a protocol.

My impression was that the blogger was using @ ViewBuilder as a workaround, to be able to return any subclass of View, because (as he explains) he was seeing compilation errors.

So what I'm saying is, if a function can return any View or subclass of View, rather than give compilation errors just designate that the func can return a variety of object types e.g. with a "factory" keyword.

Maybe a better syntax would be (for a non result builder):

func maker (string:String) -> View factory {
}

And for a result builder:

func maker (array:[String]) -> some View {
}
using (view:View) {
  self.addSubview(view); // or whatever
}

Maybe the problem then is an excess of syntactic sugar, leading the syntax diabetes?

In the bad old days of Obj-C, I think this sugar called result builder would likely be implemented with #define macros. It would be deemed a "bad hack." Could it be that Swift's designers are implementing the same bad hack but in a different way?

I glanced at the article you linked to, and I think it may have left you a bit confused about the relationship between @ViewBuilder and some View.

some View simply declares that the result of the method is one specific (but unnamed) type that conforms to View. There can be many return statements if you’d like, but they all have to return the exact same type, and that type has to conform to View.

The first version of their function fails to compile because it returns a Text from one branch and a VStack from the other, so it’s returning different types from different return statements. The way you solve this is by making both branches return a type that can contain either a Text or a VStack.

SwiftUI has a hidden type, called _ConditionalContent, that does this; a _ConditionalContent<Text, VStack> contains either a Text or a VStack, so you can return that type from both branches. But you can't construct a _ConditionalContent directly. Instead, you use a feature called result builders that puts the body of the method in a special mode where it gathers up all the values (views, in this case) that you create and assembles them into one big return value. That includes translating an ifelse statement into (in this case) a _ConditionalContent view. So if you use a result builder to create the return value, then you don’t have this mismatched-return-type problem.

Result builders are a general feature that libraries can adopt by providing a type with helper methods Swift can call. SwiftUI provides a result builder type called ViewBuilder; that’s where @ViewBuilder comes from.

My point is this: There’s no direct relationship between some View and @ViewBuilder in your code. some View imposes a requirement on your code, and @ViewBuilder is designed in a way that happens to satisfy this requirement, but you could easily use some View without @ViewBuilder or vice versa. I did a whole 45-minute WWDC session on result builders a couple years ago and barely even mentioned some View.

(P.S. The question you should be asking is, “why don’t I have to write @ViewBuilder on my var body properties? And the answer is, “because View.body already has @ViewBuilder on it, and your body inherits that for free". View doesn’t know about your random helper methods, so it can’t apply @ViewBuilder to them automatically like that.)

19 Likes

That does help clarify, thanks. I'll check out your video.

A quick question though, if the "some View" return type is limiting, why not introduce a less limiting expression like "various View"? e.g. allowing the return of View or anything derived from View.

A factory is not a function. A factory is an object that makes objects. It is not a modern concept anymore, and it's cringey to see the term in the API Design Guidelines.

Result builders are not factories or factory methods. They don't vend subtypes or existentials.

The details are very complicated, but the short version is that SwiftUI works by comparing the previous version of the body to the current one and updating what’s on screen to reflect the differences. To do that comparison, it needs both versions of the body to have the exact same type. Views like _ConditionalContent and Optional are used to tell SwiftUI about views that could be present in other versions of the body even if they aren’t there now. In other words, even when a _ConditionalContent<Text, VStack> contains a Text, SwiftUI knows that it could contain a VStack instead, and it knows to handle that possibility.

some View helps with that by guaranteeing to SwiftUI that, even if it doesn't know the specific type being returned, it at least knows that every call will always return the exact same type. A various View feature would not provide that guarantee, so SwiftUI would not be able to use it.

5 Likes

In real world code, it often comes down to a method (of a factory class) that can return an object of a class or any subclass of that. To that end, limiting return types to just one particular class isn't helpful.

This “various” type does exist; as of Swift 5.7 it’s spelled any View. It’s not very useful for reasons unrelated to the spelling, as @beccadax mentioned above.

2 Likes