Skip to content

Commit fb974bd

Browse files
[SE-0289] Update the detailed design, etc. (#1193)
1 parent ed3ea9c commit fb974bd

File tree

1 file changed

+20
-17
lines changed

1 file changed

+20
-17
lines changed

proposals/0289-result-builders.md

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ In this example, all the statements are expressions and so produce a single valu
8282
In effect, this proposal allows the creation of a new class of embedded domain-specific languages in Swift by applying *builder transformations* to the statements of a function. The power of these builder transformations is intentionally limited so that the result preserves the dynamic semantics of the original code: the original statements of the function are still executed as normal, it's just that values which would be ignored under normal semantics are in fact collected into the result. The use of an *ad hoc* protocol for the builder transformation leaves room for a wide variety of future extension, whether to support new kinds of statements or to customize the details of the transformation. A similar builder pattern was used successfully for string interpolation in [SE-0228](https://github.com/apple/swift-evolution/blob/master/proposals/0228-fix-expressiblebystringinterpolation.md).
8383

8484
Result builders have been a "hidden" feature since Swift 5.1, under the name "function builder", and the implementation (and its capabilities) have evolved since then. They are used most famously by [SwiftUI](https://developer.apple.com/xcode/swiftui/) to declaratively describe user interfaces, but others have also experimented with [building Swift syntax trees](https://swiftpack.co/package/akkyie/SyntaxBuilder), [testing](https://www.dotconferences.com/2020/02/kaya-thomas-swift-techniques-for-testing),
85-
[a Shortcuts DSL](https://github.com/a2/swift-shortcuts), [a CSS SDL](https://github.com/carson-katri/swift-css/blob/master/Sources/CSS/CSSBuilder.swift), and [an alternative SwiftPM manifest format](https://forums.swift.org/t/declarative-package-description-for-swiftpm-using-function-builders/28699). There's a GitHub repository dedicated to [awesome function builders](https://github.com/carson-katri/awesome-function-builders) with more applications.
85+
[a Shortcuts DSL](https://github.com/a2/swift-shortcuts), [a CSS DSL](https://github.com/carson-katri/swift-css/blob/master/Sources/CSS/CSSBuilder.swift), and [an alternative SwiftPM manifest format](https://forums.swift.org/t/declarative-package-description-for-swiftpm-using-function-builders/28699). There's a GitHub repository dedicated to [awesome function builders](https://github.com/carson-katri/awesome-function-builders) with more applications.
8686

8787
## Motivation
8888

@@ -180,7 +180,7 @@ header1(chapter + "1. Loomings.")
180180
header1(chapter + "2. The Carpet-Bag.")
181181
```
182182

183-
Most programmers would advise declaring this variable in as narrow a scope as possible and as close as possible to where it's going to be used. But because the entire hierarchy is an expression, and there's no easy to declare variables within expressions, every variable like this has to be declared above the entire hierarchy. (Now, it's true that there's a trick for declaring locals within expressions: you can start a closure, which gives you a local scope that you can use to declare whatever you want, and then immediately call it. But this is awkward in its own way and significantly adds to the punctuation problem.)
183+
Most programmers would advise declaring this variable in as narrow a scope as possible, and as close as possible to where it's going to be used. But because the entire hierarchy is an expression, and there's no easy way to declare variables within expressions, every variable like this has to be declared above the entire hierarchy. (Now, it's true that there's a trick for declaring locals within expressions: you can start a closure, which gives you a local scope that you can use to declare whatever you want, and then immediately call it. But this is awkward in its own way and significantly adds to the punctuation problem.)
184184

185185
Some of these problems would be solved, at least in part, if the hierarchy was built up by separate statements:
186186

@@ -254,11 +254,13 @@ These last two points (and some other considerations) strongly suggest that the
254254

255255
A *result builder type* is a type that can be used as a result builder, which is to say, as an embedded DSL for collecting partial results from the expression-statements of a function and combining them into a return value.
256256

257-
A result builder type must satisfy one basic requirement:
257+
A result builder type must satisfy two basic requirements:
258258

259259
* It must be annotated with the `@resultBuilder` attribute, which indicates that it is intended to be used as a result builder type and allows it to be used as a custom attribute.
260260

261-
In addition, to be practically useful, it must supply a sufficient set of function-builder methods to translate the kinds of functions which the DSL desires to translate.
261+
* It must supply at least one static `buildBlock` result-building method.
262+
263+
In addition, it may supply a sufficient set of other result-building methods to translate the kinds of functions which the DSL desires to translate.
262264

263265
### Result builder attributes
264266

@@ -272,7 +274,7 @@ To be useful as a result builder, the result builder type must provide a suffici
272274

273275
Result-building methods are `static` methods that can be called on the result builder type. Calls to result-building methods are type-checked as if a programmer had written `BuilderType.<methodName>(<arguments>)`, where the arguments (including labels) are as described below; therefore, all the ordinary overload resolution rules apply. However, in some cases the result builder transform changes its behavior based on whether a result builder type declares a certain method at all; it is important to note that this is a weaker check, and it may be satisfied by a method that cannot in fact ever be successfully called on the given builder type.
274276

275-
This is a quick reference for the function-builder methods currently proposed. The typing here is subtle, as it often is in macro-like features. In the following descriptions, `Expression` stands for any type that is acceptable for an expression-statement to have (that is, a raw partial result), `Component` stands for any type that is acceptable for a partial or combined result to have, and `FinalResult` stands for any type that is acceptable to be ultimately returned by the transformed function.
277+
This is a quick reference for the result-building methods currently proposed. The typing here is subtle, as it often is in macro-like features. In the following descriptions, `Expression` stands for any type that is acceptable for an expression-statement to have (that is, a raw partial result), `Component` stands for any type that is acceptable for a partial or combined result to have, and `FinalResult` stands for any type that is acceptable to be ultimately returned by the transformed function.
276278

277279
* `buildBlock(_ components: Component...) -> Component` is used to build combined results for statement blocks. It is required to be a static method in every result builder.
278280

@@ -535,7 +537,7 @@ If no `buildArray` is provided, `for`..`in` loops are not supported in the body.
535537
Statements that introduce limited availability contexts, such as `if #available(...)`, allow the use of newer APIs while still making the code backward-deployable to older versions of the libraries. A result builder that carries complete type information (such as SwiftUI's [`ViewBuilder`](https://developer.apple.com/documentation/swiftui/viewbuilder)) may need to "erase" type information from a limited availability context using `buildLimitedAvailability`. Here is a SwiftUI example borrowed from [Paul Hudson](https://www.hackingwithswift.com/quick-start/swiftui/how-to-lazy-load-views-using-lazyvstack-and-lazyhstack):
536538

537539
```swift
538-
@available(macOS 10.15, iOS 13.0)
540+
@available(macOS 10.15, iOS 13.0, *)
539541
struct ContentView: View {
540542
var body: some View {
541543
ScrollView {
@@ -587,11 +589,11 @@ if #available(macOS 11.0, iOS 14.0, *) {
587589
let v0 = LazyVStack { }
588590
let v1 = ViewBuilder.buildBlock(v0)
589591
let v2 = ViewBuilder.buildLimitedAvailability(v1)
590-
vMerged = ViewBuilder.build(first: v2)
592+
vMerged = ViewBuilder.buildEither(first: v2)
591593
} else {
592594
let v3 = VStack { }
593595
let v4 = ViewBuilder.buildBlock(v3)
594-
vMerged = ViewBuilder.build(second: v4)
596+
vMerged = ViewBuilder.buildEither(second: v4)
595597
}
596598
```
597599

@@ -971,7 +973,7 @@ enum ArrayBuilder<E>: ResultBuilder {
971973
typealias FinalResult = [E]
972974

973975
static func buildFinalResult(_ component: Component) -> FinalResult {
974-
switch components {
976+
switch component {
975977
case .expression(let e): return [e]
976978
case .block(let children): return children.flatMap(buildFinalResult)
977979
case .either(.first(let child)): return buildFinalResult(child)
@@ -1033,6 +1035,7 @@ Definition {
10331035
filter {
10341036
equals(people.age, 42)
10351037
}
1038+
}
10361039
}
10371040
```
10381041

@@ -1049,7 +1052,7 @@ let v3 = <result of transalation filter expression>
10491052
let v4 = ThingBuilder.buildBlock(v0, v1, v2, v3)
10501053
```
10511054

1052-
Such DSLs would not be change the way declarations are type checked, but would have the option to produce partial results for them, which could further eliminate boilerplate. On the other hand, without this feature one can freely use `let` to pull out subexpressions and partial computations without changing the code. For example, today one expects to be able to refactor
1055+
Such DSLs would not change the way declarations are type checked, but would have the option to produce partial results for them, which could further eliminate boilerplate. On the other hand, without this feature one can freely use `let` to pull out subexpressions and partial computations without changing the code. For example, today one expects to be able to refactor
10531056

10541057
```swift
10551058
a.doSomething().doSomethingElse()
@@ -1118,7 +1121,7 @@ Support for additional control-flow statements would weaken the declarative feel
11181121
It has been suggested that there could be two "forms" of result builders, one that matches the design in this proposal and a second, "simpler" one that handles the full breadth of the statement grammar (including all loops, `break`, `continue`, and so on) but sees only the partial results (e.g., via `buildExpression`) and not the structure (`buildBlock`, `buildEither(first:)`, etc. would not get called). The "simple result builder protocol" described above illustrates how one can get the second part of this--defining a simple result builder that receives all of the values without the structure--by building on top of this proposal. However, we should not have two forms of result builders in the language itself, with different capabilities, because it leads to confusion. If result builders gain support for additional control-flow statements (as a general feature), that should be reflected in the "simple result builder protocol" to extend the feature set for result builders that don't want the structure.
11191122

11201123
### Builder-scoped name lookup
1121-
It is common for DSLs to want to introduce shorthands which might not be unreasonable to introduce into the global scope. For example, `p` might be a reasonable name in the context of our `HTMLBuilder` DSL (rather than `paragraph`), but actually introducing a global function named `p` just for DSL use is quite unfortunate. Contextual lookups like `.p` will generally not work at the top level in DSLs because they will be interpreted as continuations of the previous statement. One could imagine having some way for the DSL to affect lexical lookup within transformed functions so that, e.g., within the transformed function one could use short names like `p`, `div`, and `h1`:
1124+
It is common for DSLs to want to introduce shorthands which might be unreasonable to introduce into the global scope. For example, `p` might be a reasonable name in the context of our `HTMLBuilder` DSL (rather than `paragraph`), but actually introducing a global function named `p` just for DSL use is quite unfortunate. Contextual lookups like `.p` will generally not work at the top level in DSLs because they will be interpreted as continuations of the previous statement. One could imagine having some way for the DSL to affect lexical lookup within transformed functions so that, e.g., within the transformed function one could use short names like `p`, `div`, and `h1`:
11221125

11231126
```swift
11241127
return body {
@@ -1141,9 +1144,9 @@ which are defined in the result builder type itself, e.g.,
11411144

11421145
```swift
11431146
extension HTMLBuilder {
1144-
static func body(_ children: [HTML]) -> HTMLNode { ... }
1145-
static func div(_ children: [HTML]) -> HTMLNode { ... }
1146-
static func p(_ children: [HTML]) -> HTMLNode { ... }
1147+
static func body(@HTMLBuilder _ children: () -> [HTML]) -> HTMLNode { ... }
1148+
static func div(@HTMLBuilder _ children: () -> [HTML]) -> HTMLNode { ... }
1149+
static func p(@HTMLBuilder _ children: () -> [HTML]) -> HTMLNode { ... }
11471150
static func h1(_ text: String) -> HTMLNode { ... }
11481151
}
11491152
```
@@ -1179,9 +1182,9 @@ Here, one can put the shorthand names (or, indeed, everything defined for the DS
11791182

11801183
```swift
11811184
extension HTMLDocument {
1182-
static func body(_ children: [HTML]) -> HTMLNode { ... }
1183-
static func div(_ children: [HTML]) -> HTMLNode { ... }
1184-
static func p(_ children: [HTML]) -> HTMLNode { ... }
1185+
static func body(@HTMLBuilder _ children: () -> [HTML]) -> HTMLNode { ... }
1186+
static func div(@HTMLBuilder _ children: () -> [HTML]) -> HTMLNode { ... }
1187+
static func p(@HTMLBuilder _ children: () -> [HTML]) -> HTMLNode { ... }
11851188
static func h1(_ text: String) -> HTMLNode { ... }
11861189
}
11871190
```

0 commit comments

Comments
 (0)