somu
(somu)
1
Overview:
I am trying to use @resultBuilder, however I have run into an issue while trying to support for loops
Problem:
I get the following compilation error at the line of of the for loop:
error: ResultBuilder.playground:29:5: error: cannot pass array of type '[String]' as variadic arguments of type 'String'
Xcode's auto-completion Signature of buildArray
Using Xcode's auto-completion the buildArray's signature is as follows
static func buildArray(_ components: [[String]]) -> [String] {
}
Fix (not sure it is the right way):
Instead of using Xcode's autocompletion signature I have defined the buildArray as follows:
static func buildArray(_ components: [[String]]) -> String {
//some logic to return string
}
Questions:
- Is the auto-completion signature wrong?
- Can buildArray be of any signature or it needs to be based on buildBlock?
- Is my Fix acceptable?
Note: Apple Swift version 5.4
Code:
@resultBuilder
struct List {
static func buildBlock(_ components: String...) -> [String] {
components
}
static func buildArray(_ components: [[String]]) -> [String] {
components.map { $0.joined(separator: "\n") }
}
}
func numberedList(@List content: () -> [String]) -> [String] {
content()
}
func buildList(from strings: [String]) -> String {
strings.enumerated().map { "\($0.offset). \($0.element)" }.joined(separator: "\n")
}
let x = numberedList {
"aaaa"
"bbbb"
"cccc"
//Following for loop causes a compilation error: cannot pass array of type '[String]' as variadic arguments of type 'String'
for _ in 1...10 {
"qqqq"
"aaaa"
}
}
print(buildList(from: x))
The Component in your builder is not String, it's actually [String], so the buildBlock should actually accept components: [String]..., and to translate a single String you need buildExpression.
Here's the full builder:
@resultBuilder
struct List {
static func buildBlock(_ components: [String]...) -> [String] {
Array(components.joined())
}
static func buildExpression(_ expression: String) -> [String] {
[expression]
}
static func buildArray(_ components: [[String]]) -> [String] {
Array(components.joined())
}
}
1 Like
somu
(somu)
3
@ExFalsoQuodlibet Thanks a ton!!
Your code works great, but I have some doubts.
Doubts:
- I thought the components was
String.... Is there a reason why you it is [String]...?
Without the for loop part, it seemed like the component was String...
"aaaa"
"bbbb"
"cccc"
- In your code you are converting every String to [String]. Just wondering if that was necessary, was my fix valid?
My fix was:
static func buildArray(_ components: [[String]]) -> String {
//some logic to return string
}
Lantua
4
The Component needs to support the results from:
buildExpression (default to Expression type),
buildEither (for if-else block),
buildBlock (for do block), and
buildArray (for for-in block), etc.
In so far, String can only potentially support the result from buildExpression. If you'd like to use [String] for other results, you should use [String] (or enum if you want single-String to remain single).
That said, it's possible to support multiple Component type, which may be useful to restrict the order of the component appearances.
func buildBlock(_: Header, _: Body, _: Footer) -> FinalResult { ... }
...
// You need to do
{
Header
Body
Footer
}
Though I don't think it's very useful in this case.
1 Like
somu
(somu)
5
@Lantua Thanks a lot!!
Still learning this, based on my crude understanding (I could be wrong), the value returned by buildArray needs to be Component type. Component is defined by buildBlock
In my case buildBlock was as follows:
static func buildBlock(_ components: String...) -> [String] {
components
}
So Component is String
Then buildArray would need to return Component, in this case String
static func buildArray(_ components: [[String]]) -> String {
//some logic to return string
}
So the entire buildArray needs to build down to a single Component, so the buildBlock can process it
The definition of buildBlock is:
static func buildBlock(_ components: Component...) -> Component { ... }
It takes a variadic list of Component instances, and return a single Component instance.
So in your case it must be either
static func buildBlock(_ components: String...) -> String { ... }
or
static func buildBlock(_ components: [String]...) -> [String] { ... }
In your case it's probably easier to use [String] as Component. If you want to return directly a String from you builder function, you can add:
static func buildFinalResult(_ component: [String]) -> String { ... }
1 Like
Lantua
7
Personally, I wouldn't use the term Component as though each builder has exactly one. It's quite possible to have many (looking at you, ViewBuilder).
In any case, if you have: buildArray(_: [C1]) -> C2, you'd need at least:
-
buildBlock(...) -> C1, for block inside for-loop,
-
buildBlock(..., _: C2, ...) -> C3 for block containing for-loop.
And it's far easier if C1, C2, and C3 are all [String], which would be your "Component" type. In that case, you can also just use buildBlock(_: [String]...) -> [String] for both buildBlock.
1 Like
somu
(somu)
8
@ExFalsoQuodlibet @Lantua Thank a lot!!!
I think I am beginning to understand a little better.
Now I understand why they used protocol in the Advanced Operators — The Swift Programming Language (Swift 5.7) it does give some flexibility with protocol as they could be different types.
Thanks a ton!! saved my day ... am sure I will have more questions as I start to use them more 
Consider also that if you have
static func buildArray(_ components: [[String]]) -> String {
//some logic to return string
}
then the entire for loop would be rendered down to a single String, which is not necessarily what you'd want. In general I would advise to initially design builders with a single Component in mind, and think about how to support it in all builders functions, adding buildExpressions when some specific expression must be resolved into a Component instance. Then you can expand the definition of Component as @Lantua mentioned, if you need special behavior, like specific ordering of elements in the builder function, or different behavior based of which "components" are merged in buildBlock.
1 Like
somu
(somu)
10
I think that is a good approach, boiling it down to a string I was finding it difficult to build the logic to achieve what I wanted. Thanks a lot!!