I dislike these languages features that add hidden complexity. They look great on slides, but IME they make debugging and understanding the code harder. Instead of adding new features to address current limitations, I'd rather those limitations be met directly.
I also don't find the motivation section of the proposal compelling. It's mostly demonstrating the difficulty of working with poorly abstracted code. This code is painful because it's writing a novel directly in HTML, skipping a meaningful semantic layer:
let chapter = spellOutChapter ? "Chapter " : ""
let d1header = useChapterTitles ? [header1(chapter + "1. Loomings.")] : []
let d1p1 = paragraph(["Call me Ishmael. Some years ago"])
let d1p2 = paragraph(["There is now your insular city"])
let d1 = division(d1header + [d1p1, d1p2])
let d2header = useChapterTitles ? [header1(chapter + "2. The Carpet-Bag.")] : []
let d2p1 = paragraph(["I stuffed a shirt or two"])
let d2 = division(d2header + [d2p1])
return body([d1, d2])
A DSL for writing books shouldn't use HTML directly. It should create an appropriate semantic layer for books:
struct Book {
struct Chapter {
var title: String
var paragraphs: [String]
}
var chapters: [Chapter]
}
Once you do that, representing the book becomes easy:
let mobyDick = Book(chapters: [
Book.Chapter(
title: "Loomings.",
paragraphs: [
"Call me Ishmael. Some years ago",
"There is now your insular city",
]
),
Book.Chapter(
title: "The Carpet-Bag.",
paragraphs: [
"I stuffed a shirt or two"
]
),
]
That's with only the synthesized constructors, but you can simplify this more with additional constructors and/or the ExpressibleBy*Literal protocols:
let mobyDick: Book = [
"Loomings": [
"Call me Ishmael. Some years ago",
"There is now your insular city",
],
"The Carpet-Bag.": [
"I stuffed a shirt or two"
],
]
(Probably still not how you want to write a book, but it's semantically precise.)
Once you represent books semantically, the code to display chapter titles becomes straightforward and contained:
extension Book {
enum ChapterStyle {
case none
case number
case spelledOut
}
func render(style: ChapterStyle) -> HTML {
let divs = zip(1..., chapters)
.map { number, chapter -> HTMLNode in
let header: String?
switch style {
case none:
header = nil
case number:
header = "\(number). \(chapter.title)"
case spelledOut:
header = "Chapter \(number). \(chapter.title)"
}
let paragraphs = chapter.paragraphs.map { paragraph([$0]) }
if let header = header {
return [header1(header)] + paragraphs
} else {
return paragraphs
}
}
.map(division)
return body(divs)
}
}
This removes certain classes of bugs (e.g. incorrect chapter numbers, inconsistency between chapters, etc.), separates concerns, improves testability, and doesn't require new language features.
Coming up with good examples is hard, but I think the proposal ought to do better to demonstrate the real world need for result builders by giving an example where result builders are the best solution.