Associate result builders with types

Some types and protocols are strongly associated with a result builder (View and its @ViewBuilder from SwiftUI comes to mind, but there are several others).

In a View conforming type you need to remember to decorate your private → some View returning functions with @ViewBuilder or you'll get compilation errors if you introduce e.g. conditionals.

In the following code, the commenting in or our the line causes the code to compile, or not compile. It is almost always the intention of the user to use @ViewBuilder on this function:

struct MyView: View {
  // ...

  private func buttonRow() -> some View {
    if condition {
       HStack {
         createButton("Foo", true, "fooo")
         createButton("Yes", true, "yeees")
         // createButton("Bar", false, "barrrr")
       }
    } else {
       HStack {
         createButton("Qux", true, "quiz?")
         createButton("Frup", true, "frappapapapapp")
         createButton("Boik", false, "boioioink")
       }
    }
  }
}

Shold Swift have some mechanism of associating a result builder with a type or protocol, so that all of these syntaxes Just Do The Rigth Thing?

// auto-apply @HTMLBuilder to functions that return
// `some` protocols with associated result builder?
func foo() -> some HTML { ... }  

// auto-apply @HTMLBuilder and infer return value when
// constructing values with a closure?
let html = HTML { ... }

These ideas are not mature, I'm not sure if we should just apply the attribute to the type (currently resultbuilders aren't applicable on types or extensions):

@HTMLBuilder
protocol HTML { ... }

Or maybe some associatedtype-magic?

I'm not sure. But Do you think the idea has merit?

4 Likes

I think this has merit. I shared my thoughts on something like this a while ago:

I think in addition to what you said, there is another benefit in associating result builders with types, which is that it could allow for more natural function signatures. To quote that post:

Quote

I wish that result builders and other attributes were required on protocol conformers rather than being implicitly applied. When I was learning about result builders it took me a while to understand why something like body didn’t require ViewBuilder but all of my variables and functions did. I think that implicitly hiding a declaration like this would make result builders even more “magic” and equally more frustrating for everyone.

4 Likes

I have to say, I haven’t been working much with ResultBuilder before besides SwiftUI so I cannot comment on any advantages of implicitly applying it or not.

I can just say that I apply @ViewBuilder to any var or func that is part of a SwiftUI view so I can easily see what is UI and what not, so just from the looks perspective, I prefer it staying there explicit.

If there are some more technical differences, I‘m happy to change my opinion though.

I like the premise that this case is probably well diagnosable by the compiler, yet I am hesitant about implicit behaviour there. Maybe I don’t want builder there. I think it would make a ton of difference if compiler could suggest as fix-it to add an annotation in such cases.

2 Likes

Yes, the more I think about it, I also see the downsides of implicitly applying attributes to functions. I'm especially sympathetic to the point raised by @flexlixrup above, that seeing the @ViewBuilder attributes makes code more glanceable, and I do think we should prioritize readable code, over easy to write code.

But the problem that subtle differences in a function implementation can render code uncompilable is certainly real. Could we somehow allow the type to be decorated, and that it mostly affects compiler diagnostics?

I.e. that a e.g. HTML-returning (or some HTML-returning) function could get better diagnostics if the HTML was decorated with @HTMLBuilder, or had an associatedtype ResultBuilder = HTMLBuilder?

1 Like