DSL과 SwiftUI: 도메인 특화 언어의 이해와 활용
들어가며
우리가 일상적으로 사용하는 프로그래밍 언어들은 대부분 범용적인 목적으로 설계되었습니다. Swift, Java, Python 같은 언어들은 웹 개발부터 모바일 앱, 데이터 분석까지 다양한 분야에서 활용할 수 있죠. 하지만 때로는 특정 분야에서 더 직관적이고 효율적으로 문제를 해결할 수 있는 전용 언어가 필요합니다. 이것이 바로 DSL(Domain Specific Language)의 개념입니다.
SwiftUI는 이러한 DSL의 훌륭한 예시입니다. UI 구성이라는 특정 도메인에 특화되어 설계된 언어로, 개발자가 더 직관적이고 선언적인 방식으로 사용자 인터페이스를 구성할 수 있게 해줍니다. 이 글에서는 DSL의 기본 개념부터 시작해서 SwiftUI가 어떻게 DSL로 작동하는지, 그리고 이를 가능하게 하는 @resultBuilder와 @ViewBuilder의 역할까지 깊이 있게 살펴보겠습니다.
DSL이란 무엇인가?
DSL의 정의와 특징
DSL(Domain Specific Language)은 특정 응용 분야나 도메인에 특화된 컴퓨터 언어입니다. 범용 프로그래밍 언어(GPL: General Purpose Language)와 달리, DSL은 특정 문제 영역을 효과적으로 해결하기 위해 설계됩니다.
우리는 이미 일상에서 많은 DSL을 사용하고 있습니다. SQL은 데이터베이스 조작에 특화된 언어이고, CSS는 웹 스타일링에, HTML은 문서 구조 표현에 특화되어 있습니다. 이들 각각은 해당 도메인에서 매우 직관적이고 표현력이 뛰어납니다.
-- SQL: 데이터베이스 도메인 DSL
SELECT name, age
FROM users
WHERE age > 18
ORDER BY name ASC
이 SQL 문장을 보면, 데이터베이스에 익숙한 사람이라면 "18세 이상 사용자의 이름과 나이를 이름 순으로 정렬해서 가져와라"라는 의미를 즉시 이해할 수 있습니다. 만약 이를 일반적인 프로그래밍 언어로 표현한다면 훨씬 복잡해질 것입니다.
DSL의 장점
DSL이 제공하는 주요 장점들을 살펴보겠습니다.
표현력(Expressiveness): DSL은 해당 도메인의 개념을 직접적으로 표현할 수 있어 코드가 매우 간결해집니다. 복잡한 개념을 몇 줄의 코드로 표현할 수 있죠.
가독성(Readability): 도메인 전문가가 보기에 자연스럽고 이해하기 쉽습니다. 비개발자도 어느 정도 이해할 수 있는 경우가 많습니다.
생산성(Productivity): 해당 도메인의 문제 해결에 집중할 수 있어 개발 속도가 빨라집니다. 반복적인 코드를 줄이고 핵심 로직에 집중할 수 있습니다.
오류 감소(Error Reduction): 도메인에 특화된 제약 조건을 언어 차원에서 제공하므로 실수를 줄일 수 있습니다.
External DSL vs Internal DSL
DSL은 구현 방식에 따라 두 가지로 나눌 수 있습니다.
External DSL은 완전히 새로운 문법을 가진 독립적인 언어입니다. JSON, XML, SQL 등이 이에 해당합니다. 이들은 자체적인 파서와 인터프리터를 가지고 있습니다.
{
"name": "홍길동",
"age": 30,
"skills": ["Swift", "iOS", "SwiftUI"]
}
Internal DSL은 기존 호스트 언어(여기서는 Swift) 안에서 특정 도메인을 위한 API나 문법을 제공하는 방식입니다. SwiftUI가 바로 이런 Internal DSL의 좋은 예시입니다.
GPL vs DSL: Swift와 SwiftUI 비교
Swift: 범용 프로그래밍 언어
Swift는 범용 프로그래밍 언어입니다. iOS 앱 개발부터 서버 사이드 개발, 시스템 프로그래밍까지 다양한 분야에서 활용할 수 있죠. 이런 유연성이 GPL의 큰 장점입니다.
// Swift로 계산기 클래스 구현
class Calculator {
private var result: Double = 0
func add(_ value: Double) -> Calculator {
result += value
return self
}
func multiply(_ value: Double) -> Calculator {
result *= value
return self
}
func getResult() -> Double {
return result
}
}
// 사용 예시
let calculator = Calculator()
.add(10)
.multiply(2)
.add(5)
print(calculator.getResult()) // 25.0
이 코드는 Swift의 범용적 특성을 잘 보여줍니다. 객체 지향 프로그래밍, 함수형 프로그래밍 등 다양한 패러다임을 지원하며, 어떤 종류의 애플리케이션이든 만들 수 있습니다.
SwiftUI: UI 도메인 특화 언어
반면 SwiftUI는 UI 구성이라는 특정 도메인에 특화된 DSL입니다. UI를 구성하는 데 필요한 개념들이 언어의 핵심 요소로 내장되어 있습니다.
// SwiftUI로 동일한 계산기 UI 구현
struct CalculatorView: View {
@State private var result: Double = 0
@State private var currentInput: String = ""
var body: some View {
VStack(spacing: 20) {
// 결과 표시 영역
Text(String(result))
.font(.largeTitle)
.foregroundColor(.primary)
.frame(maxWidth: .infinity, alignment: .trailing)
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
// 입력 영역
TextField("숫자를 입력하세요", text: $currentInput)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.decimalPad)
// 버튼 영역
HStack(spacing: 15) {
Button("더하기") {
addToResult()
}
.buttonStyle(.borderedProminent)
Button("곱하기") {
multiplyResult()
}
.buttonStyle(.borderedProminent)
Button("초기화") {
resetCalculator()
}
.buttonStyle(.bordered)
}
}
.padding()
}
private func addToResult() {
if let value = Double(currentInput) {
result += value
currentInput = ""
}
}
private func multiplyResult() {
if let value = Double(currentInput) {
result *= value
currentInput = ""
}
}
private func resetCalculator() {
result = 0
currentInput = ""
}
}
이 SwiftUI 코드를 보면 UI 구성에 필요한 개념들이 언어의 핵심 요소로 제공되는 것을 알 수 있습니다. VStack, HStack, Text, Button 등은 모두 UI 도메인에 특화된 구성 요소들입니다.
선언적 vs 명령적 프로그래밍
SwiftUI의 가장 큰 특징 중 하나는 선언적 프로그래밍 패러다임을 따른다는 것입니다. 이는 DSL의 일반적인 특성이기도 합니다.
명령적 프로그래밍은 "어떻게(How)" 할 것인지에 초점을 맞춥니다. 단계별로 무엇을 해야 하는지 명시합니다.
// UIKit (명령적 방식)
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 20
stackView.distribution = .fill
let titleLabel = UILabel()
titleLabel.text = "계산기"
titleLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold)
titleLabel.textAlignment = .center
stackView.addArrangedSubview(titleLabel)
let resultLabel = UILabel()
resultLabel.text = "0"
resultLabel.font = UIFont.systemFont(ofSize: 36)
resultLabel.textAlignment = .right
resultLabel.backgroundColor = UIColor.systemGray6
stackView.addArrangedSubview(resultLabel)
let addButton = UIButton(type: .system)
addButton.setTitle("더하기", for: .normal)
addButton.backgroundColor = UIColor.systemBlue
addButton.setTitleColor(.white, for: .normal)
addButton.layer.cornerRadius = 8
stackView.addArrangedSubview(addButton)
view.addSubview(stackView)
선언적 프로그래밍은 "무엇을(What)" 원하는지에 초점을 맞춥니다. 최종 결과가 어떤 모습이어야 하는지 기술합니다.
// SwiftUI (선언적 방식)
VStack(spacing: 20) {
Text("계산기")
.font(.title)
.fontWeight(.bold)
Text("0")
.font(.largeTitle)
.frame(maxWidth: .infinity, alignment: .trailing)
.padding()
.background(Color.gray.opacity(0.1))
Button("더하기") {
// 액션
}
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(8)
}
선언적 방식이 훨씬 간결하고 의도가 명확하게 드러나는 것을 알 수 있습니다. 이는 UI라는 도메인의 특성을 잘 활용한 결과입니다.
@resultBuilder: DSL 제작의 핵심 도구
@resultBuilder의 등장 배경
Swift 5.4에서 도입된 @resultBuilder는 개발자가 Swift 언어 내에서 자신만의 DSL을 만들 수 있게 해주는 강력한 도구입니다. 이전에는 Function Builder라고 불렸지만, 더 명확한 의미를 전달하기 위해 이름이 변경되었습니다.
@resultBuilder가 없다면 SwiftUI와 같은 선언적 문법을 구현하기 매우 어려웠을 것입니다. 여러 개의 View를 자연스럽게 나열하고, 조건문과 반복문을 섞어서 사용하는 것이 불가능했겠죠.
@resultBuilder의 동작 원리
@resultBuilder는 컴파일 타임에 특별한 문법 변환을 수행합니다. 개발자가 작성한 코드를 분석해서 미리 정의된 메서드 호출로 변환하는 것이죠.
// 간단한 문자열 빌더 예제
@resultBuilder
struct StringBuilder {
// 여러 문자열을 하나로 합치기
static func buildBlock(_ components: String...) -> String {
return components.joined(separator: " ")
}
// 조건부 문자열 처리
static func buildOptional(_ component: String?) -> String {
return component ?? ""
}
// if-else 문 처리
static func buildEither(first component: String) -> String {
return component
}
static func buildEither(second component: String) -> String {
return component
}
// 배열(반복문) 처리
static func buildArray(_ components: [String]) -> String {
return components.joined(separator: ", ")
}
}
이제 이 StringBuilder를 사용해서 문자열 DSL을 만들 수 있습니다.
@StringBuilder
func createGreeting(name: String, showTime: Bool) -> String {
"안녕하세요"
name + "님"
if showTime {
"현재 시간은"
DateFormatter().string(from: Date())
}
"좋은 하루 되세요!"
}
let greeting = createGreeting(name: "홍길동", showTime: true)
// 결과: "안녕하세요 홍길동님 현재 시간은 2025-05-24 좋은 하루 되세요!"
@resultBuilder의 변환 과정
위의 코드가 컴파일될 때 실제로는 다음과 같이 변환됩니다:
func createGreeting(name: String, showTime: Bool) -> String {
return StringBuilder.buildBlock(
"안녕하세요",
name + "님",
StringBuilder.buildOptional(showTime ?
StringBuilder.buildBlock(
"현재 시간은",
DateFormatter().string(from: Date())
) : nil
),
"좋은 하루 되세요!"
)
}
이렇게 @resultBuilder는 자연스러운 문법을 복잡한 메서드 호출로 변환해주는 역할을 합니다.
변환과정 자세히 알아보기
컴파일러의 시각에서 보는 변환 과정
Swift 컴파일러가 @resultBuilder를 만나면 마치 번역가처럼 작동합니다. 우리가 쓴 "자연어 같은 코드"를 "기계가 이해할 수 있는 메서드 호출"로 번역하는 것이죠.
1단계: 구문 분석 (Syntax Analysis)
컴파일러는 먼저 우리 코드의 구조를 파악합니다. 예제를 다시 살펴보면서 컴파일러가 어떻게 "읽어내는지" 보겠습니다.
@StringBuilder
func createGreeting(name: String, showTime: Bool) -> String {
"안녕하세요" // 1. 문자열 리터럴
name + "님" // 2. 표현식 결과 (String)
if showTime { // 3. 조건문 시작
"현재 시간은" // 4. 조건부 문자열 1
DateFormatter().string(from: Date()) // 5. 조건부 문자열 2
} // 6. 조건문 종료
"좋은 하루 되세요!" // 7. 문자열 리터럴
}
컴파일러는 이 코드를 보고 다음과 같이 구조를 인식합니다: "StringBuilder라는 result builder를 사용하는 함수가 있고, 그 안에 여러 개의 문자열 표현식과 하나의 if문이 있구나"
2단계: 구문 요소 식별과 매핑
이제 컴파일러는 각 구문 요소를 적절한 builder 메서드에 매핑합니다. 이때 미리 정의된 규칙을 따릅니다:
// StringBuilder에 정의된 메서드들을 다시 보면
@resultBuilder
struct StringBuilder {
// 여러 요소를 순서대로 나열 → buildBlock 사용
static func buildBlock(_ components: String...) -> String
// if문으로 조건부 요소 → buildOptional 사용
static func buildOptional(_ component: String?) -> String
// if-else문 → buildEither 사용
static func buildEither(first component: String) -> String
static func buildEither(second component: String) -> String
}
컴파일러는 코드의 각 부분을 다음과 같이 해석합니다.
순차적 나열 (1, 2, 7번)
"안녕하세요"
name + "님"
"좋은 하루 되세요!"
→ "이건 여러 요소를 순서대로 나열한 거니까 buildBlock을 써야겠다"
조건문 (3-6번)
if showTime {
"현재 시간은"
DateFormatter().string(from: Date())
}
→ "이건 조건부 코드니까 buildOptional을 써야겠다. 그리고 조건문 안의 여러 요소는 또 다른 buildBlock으로 묶어야겠다"
3단계: 중첩 구조 처리 (Nested Structure Processing)
여기서 흥미로운 점은 컴파일러가 중첩된 구조를 어떻게 처리하는지입니다. 조건문 안의 두 문자열을 먼저 처리하고, 그 결과를 조건문 전체에 적용합니다.
// 먼저 조건문 내부 처리
if showTime {
"현재 시간은" // 내부 요소 1
DateFormatter().string(from: Date()) // 내부 요소 2
}
// 컴파일러가 변환: 내부 먼저 buildBlock으로 묶기
if showTime {
StringBuilder.buildBlock(
"현재 시간은",
DateFormatter().string(from: Date())
)
}
// 그 다음 조건문 전체를 buildOptional로 감싸기
StringBuilder.buildOptional(
showTime ? StringBuilder.buildBlock(
"현재 시간은",
DateFormatter().string(from: Date())
) : nil
)
4단계: 최종 변환 (Final Transformation)
모든 부분을 분석한 후, 컴파일러는 전체를 하나의 큰 buildBlock 호출로 묶습니다:
// 최종 변환된 코드
func createGreeting(name: String, showTime: Bool) -> String {
return StringBuilder.buildBlock(
"안녕하세요", // 첫 번째 요소
name + "님", // 두 번째 요소
StringBuilder.buildOptional( // 세 번째 요소 (조건부)
showTime ? StringBuilder.buildBlock(
"현재 시간은",
DateFormatter().string(from: Date())
) : nil
),
"좋은 하루 되세요!" // 네 번째 요소
)
}
컴파일러가 사용하는 변환 규칙들
컴파일러는 미리 정의된 패턴 매칭 규칙을 사용해서 이런 변환을 수행합니다. 각 구문 패턴이 어떤 메서드로 변환되는지 살펴보겠습니다.
규칙 1: 순차적 나열 → buildBlock
// 이런 패턴을 보면
expression1
expression2
expression3
// 이렇게 변환
BuilderType.buildBlock(expression1, expression2, expression3)
규칙 2: 조건문 → buildOptional 또는 buildEither
// if 문 (else 없음)
if condition {
content
}
// 변환 →
BuilderType.buildOptional(condition ? content : nil)
// if-else 문
if condition {
trueContent
} else {
falseContent
}
// 변환 →
BuilderType.buildEither(first: condition ? trueContent : falseContent)
// 또는
BuilderType.buildEither(second: !condition ? falseContent : trueContent)
규칙 3: 반복문 → buildArray
// for 문이나 배열 처리
for item in items {
processItem(item)
}
// 변환 →
BuilderType.buildArray(items.map { item in processItem(item) })
실제 SwiftUI에서의 복잡한 변환 예제
이제 더 복잡한 SwiftUI 예제로 이 과정을 확인해보겠습니다.
// 우리가 작성한 복잡한 SwiftUI 코드
@ViewBuilder
var complexView: some View {
Text("제목")
if user.isLoggedIn {
Text("환영합니다, \(user.name)님!")
if user.hasNotifications {
Button("알림 확인") { }
}
} else {
Text("로그인이 필요합니다")
Button("로그인") { }
}
ForEach(items) { item in
Text(item.title)
}
}
컴파일러가 이를 어떻게 변환하는지 단계별로 봅시다:
1단계: 가장 안쪽 조건문부터 처리
// user.hasNotifications 조건문
ViewBuilder.buildOptional(
user.hasNotifications ? Button("알림 확인") { } : nil
)
2단계: 로그인 상태 조건문의 각 분기 처리
// if user.isLoggedIn 분기 (true case)
ViewBuilder.buildBlock(
Text("환영합니다, \(user.name)님!"),
ViewBuilder.buildOptional(
user.hasNotifications ? Button("알림 확인") { } : nil
)
)
// else 분기 (false case)
ViewBuilder.buildBlock(
Text("로그인이 필요합니다"),
Button("로그인") { }
)
3단계: 전체 if-else를 buildEither로 감싸기
ViewBuilder.buildEither(
first: user.isLoggedIn ? ViewBuilder.buildBlock(
Text("환영합니다, \(user.name)님!"),
ViewBuilder.buildOptional(
user.hasNotifications ? Button("알림 확인") { } : nil
)
) : nil,
second: !user.isLoggedIn ? ViewBuilder.buildBlock(
Text("로그인이 필요합니다"),
Button("로그인") { }
) : nil
)
4단계: 최종적으로 모든 것을 buildBlock으로 묶기
// 최종 변환된 코드 (의사 코드)
var complexView: some View {
return ViewBuilder.buildBlock(
Text("제목"), // 첫 번째 요소
ViewBuilder.buildEither( // 두 번째 요소 (복잡한 조건문)
first: user.isLoggedIn ? ViewBuilder.buildBlock(
Text("환영합니다, \(user.name)님!"),
ViewBuilder.buildOptional(
user.hasNotifications ? Button("알림 확인") { } : nil
)
) : nil,
second: !user.isLoggedIn ? ViewBuilder.buildBlock(
Text("로그인이 필요합니다"),
Button("로그인") { }
) : nil
),
ForEach(items) { item in // 세 번째 요소 (반복문)
Text(item.title)
}
)
}
컴파일러가 이 모든 걸 어떻게 알까?
여기서 정말 흥미로운 점은 "컴파일러가 어떻게 이런 복잡한 변환 규칙을 알고 있느냐"는 것입니다. 답은 Swift 언어 명세에 이런 규칙들이 명시되어 있기 때문입니다.
Swift 컴파일러에는 @resultBuilder를 처리하는 전용 모듈이 있고, 이 모듈이 다음과 같은 작업을 수행합니다.
- 구문 트리 분석: 코드를 추상 구문 트리(AST)로 파싱
- 패턴 매칭: 각 구문 패턴을 미리 정의된 규칙과 매칭
- 메서드 검색: 해당 @resultBuilder 타입에서 적절한 메서드 찾기
- 코드 생성: 원본 코드를 메서드 호출로 대체하는 새로운 AST 생성
이 모든 과정이 컴파일 타임에 일어나므로, 런타임에는 변환된 코드만 실행됩니다. 그래서 성능 손실 없이 자연스러운 DSL 문법을 사용할 수 있는 것이죠.
여기서 하나 주의할 점은 @resultBuilder가 하는 일은 구조 변환이지 값 평가가 아니다라는 점입니다. 컴파일 과정에서 변환이 일어난다는 말 때문에 헷갈릴 수 있는데 실제 값 평가는 런타임에 이루어지며 어떤 구조로 조합할지에 대해서만 사전에 변환 작업이 이루어집니다.
@ViewBuilder: SwiftUI DSL의 핵심 엔진
@ViewBuilder의 역할
@ViewBuilder는 SwiftUI에서 제공하는 특별한 @resultBuilder입니다. SwiftUI의 선언적 UI 구성을 가능하게 하는 핵심 요소죠. 여러 개의 View를 자연스럽게 조합하고, 조건부 렌더링과 반복 렌더링을 직관적으로 표현할 수 있게 해줍니다.
@ViewBuilder 없이는 불가능한 것들
@ViewBuilder가 없다면 SwiftUI에서 다음과 같은 코드를 작성할 수 없을 것입니다.
// 이런 자연스러운 코드가 불가능했을 것
VStack {
Text("제목")
Text("부제목")
if user.isLoggedIn {
Text("환영합니다, \(user.name)님!")
Button("로그아웃") {
logout()
}
} else {
Button("로그인") {
showLoginSheet = true
}
}
ForEach(items) { item in
ItemRow(item: item)
}
}
대신 모든 것을 명시적으로 컨테이너에 감싸야 했을 것입니다.
// @ViewBuilder 없이 작성해야 했을 코드
VStack {
TupleView((
Text("제목"),
Text("부제목"),
Group {
if user.isLoggedIn {
VStack {
Text("환영합니다, \(user.name)님!")
Button("로그아웃") {
logout()
}
}
} else {
Button("로그인") {
showLoginSheet = true
}
}
},
ForEach(items) { item in
ItemRow(item: item)
}
))
}
@ViewBuilder의 내부 동작
@ViewBuilder가 어떻게 작동하는지 좀 더 자세히 살펴보겠습니다.
// 우리가 작성하는 코드
VStack {
Text("첫 번째")
Text("두 번째")
if showThird {
Text("세 번째")
}
}
이 코드는 컴파일 타임에 다음과 같이 변환됩니다:
// 실제 변환된 코드
VStack {
ViewBuilder.buildBlock(
Text("첫 번째"),
Text("두 번째"),
ViewBuilder.buildOptional(showThird ? Text("세 번째") : nil)
)
}
ViewBuilder의 핵심 메서드들
SwiftUI의 ViewBuilder는 다양한 상황을 처리하기 위한 메서드들을 제공합니다.
@resultBuilder
public struct ViewBuilder {
// 단일 View 처리
public static func buildBlock<Content>(_ content: Content) -> Content
where Content : View
// 두 개 View 조합
public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)>
where C0 : View, C1 : View
// 세 개 View 조합 (최대 10개까지 오버로드됨)
public static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2) -> TupleView<(C0, C1, C2)>
where C0 : View, C1 : View, C2 : View
// 조건부 View (옵셔널)
public static func buildOptional<Content>(_ content: Content?) -> Content?
where Content : View
// if-else 문 처리
public static func buildEither<TrueContent, FalseContent>(first content: TrueContent) -> _ConditionalContent<TrueContent, FalseContent>
where TrueContent : View, FalseContent : View
public static func buildEither<TrueContent, FalseContent>(second content: FalseContent) -> _ConditionalContent<TrueContent, FalseContent>
where TrueContent : View, FalseContent : View
// 배열/반복문 처리
public static func buildArray<Content>(_ components: [Content]) -> ForEach<Range<Int>, Int, Content>
where Content : View
}
결론
DSL(Domain Specific Language)은 특정 도메인의 문제를 그 도메인의 언어로 자연스럽게 표현할 수 있게 해주는 강력한 도구입니다. SwiftUI는 UI 구성이라는 도메인에 특화된 훌륭한 DSL의 예시이며, 이를 통해 개발자는 더 직관적이고 선언적인 방식으로 사용자 인터페이스를 구성할 수 있습니다.
Swift의 @resultBuilder는 이러한 DSL을 언어 내부에서 구현할 수 있게 해주는 메타 프로그래밍 도구입니다. 컴파일 타임에 자연스러운 문법을 복잡한 메서드 호출로 변환하여, 마치 새로운 언어를 사용하는 것 같은 경험을 제공합니다.
@ViewBuilder는 SwiftUI DSL의 핵심 엔진으로, 여러 View의 조합, 조건부 렌더링, 반복 렌더링 등을 자연스럽게 처리할 수 있게 해줍니다. 이를 통해 복잡한 UI 로직도 매우 직관적이고 읽기 쉬운 코드로 표현할 수 있습니다.
실제 프로젝트에서는 @resultBuilder를 활용하여 다양한 도메인별 DSL을 만들 수 있습니다. 레이아웃 구성, 폼 검증, 네트워크 요청 체이닝, 상태 관리 등 각각의 도메인에 특화된 DSL을 만들면 코드의 가독성과 유지보수성을 크게 향상시킬 수 있습니다.
DSL의 핵심은 "해당 도메인의 전문가가 자연스럽게 이해할 수 있는 언어"를 만드는 것입니다. 이를 통해 코드는 단순한 명령어의 나열이 아닌, 도메인 지식을 표현하는 언어가 됩니다. SwiftUI와 @resultBuilder는 이러한 철학을 Swift 생태계에서 실현할 수 있게 해주는 훌륭한 도구들입니다.
앞으로 iOS 개발을 할 때, 단순히 SwiftUI의 기능을 사용하는 것을 넘어서 DSL의 관점에서 접근해보시기 바랍니다. 여러분만의 도메인별 DSL을 만들어 보고, 팀 내에서 공통으로 사용할 수 있는 선언적 API를 설계해보세요. 이를 통해 더 표현력 있고 유지보수하기 쉬운 코드를 작성할 수 있을 것입니다.