diff --git a/0028-html-dsl/HTML.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift b/0028-html-dsl/HTML.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift
index a084f3f6..2697cb14 100644
--- a/0028-html-dsl/HTML.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift
+++ b/0028-html-dsl/HTML.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift
@@ -1,5 +1,5 @@
/*:
- # An HTML DSK
+ # An HTML DSL
## Exercises
diff --git a/0029-dsls-vs-templating-languages/.gitignore b/0029-dsls-vs-templating-languages/.gitignore
new file mode 100644
index 00000000..02c08753
--- /dev/null
+++ b/0029-dsls-vs-templating-languages/.gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
diff --git a/0029-dsls-vs-templating-languages/Makefile b/0029-dsls-vs-templating-languages/Makefile
new file mode 100644
index 00000000..7a65bed3
--- /dev/null
+++ b/0029-dsls-vs-templating-languages/Makefile
@@ -0,0 +1,12 @@
+init:
+ git submodule update --init
+ xcrun swift package --package-path Vendor/Stencil generate-xcodeproj
+
+install-mm-xcodeproj:
+ @ls MyApp.xcodeproj/GeneratedModuleMap | xargs -n1 -I '{}' $(SUDO) mkdir -p "$(FRAMEWORKS_PATH)/{}.framework"
+ @ls MyApp.xcodeproj/GeneratedModuleMap | xargs -n1 -I '{}' $(SUDO) cp "./MyApp.xcodeproj/GeneratedModuleMap/{}/module.modulemap" "$(FRAMEWORKS_PATH)/{}.framework/module.map"
+
+SDK_PATH = $(shell xcrun --show-sdk-path 2>/dev/null)
+FRAMEWORKS_PATH = $(SDK_PATH)/System/Library/Frameworks
+SUDO = sudo --prompt=$(SUDO_PROMPT)
+SUDO_PROMPT = " đ Please enter your password: "
diff --git a/0029-dsls-vs-templating-languages/README.md b/0029-dsls-vs-templating-languages/README.md
new file mode 100644
index 00000000..f62a396e
--- /dev/null
+++ b/0029-dsls-vs-templating-languages/README.md
@@ -0,0 +1,14 @@
+## [Point-Free](https://www.pointfree.co)
+
+> #### This directory contains code from Point-Free Episode: [DSLs vs. Templating Languages](https://www.pointfree.co/episodes/ep29-dsls-vs-templating-languages)
+>
+> Templating languages are the most common way to render HTML in web frameworks, but we donât think they are the best way. We compare templating languages to the DSL we previously built, and show that the DSL fixes many problems that templates have, while also revealing amazing compositions that were previously hidden.
+
+### Getting Started
+
+* Clone repo
+* `cd` into this directory
+* Run `make`
+* Open `TemplatingLanguages.xcworkspace`
+* Build `cmd+B`
+* Open the playground
diff --git a/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Pages/01-episode.xcplaygroundpage/Contents.swift b/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Pages/01-episode.xcplaygroundpage/Contents.swift
new file mode 100644
index 00000000..0d88fd9f
--- /dev/null
+++ b/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Pages/01-episode.xcplaygroundpage/Contents.swift
@@ -0,0 +1,143 @@
+
+import Stencil
+
+struct _User {
+ let _id: Int
+}
+
+try Template(templateString: "{{ user.id | uppercase }}").render(["user": _User(_id: 42)])
+
+
+let template = Template.init(stringLiteral: """
+
+ {% for user in users %}
+ - {{ user.name }}
+ {% endfor %}
+
+""")
+
+//print(try template.render(["users": [["name": "Blob"], ["name": "Blob Jr."], ["name": "Blob Sr."]]]))
+
+let name = "Blob"
+//print(
+// render(
+// header([
+// h1([.text(name)])
+// ])
+// )
+//)
+
+func greet(name: String) -> Node {
+ return header([
+ h1([.text(name.uppercased())])
+ ])
+}
+
+print(render(greet(name: "Blob")))
+
+struct User {
+ let name: String
+ let isAdmin: Bool
+}
+
+func adminDetail(user: User) -> [Node] {
+ guard user.isAdmin else { return [] }
+ return [
+ header([
+ h1([.text("Welcome admin: \(user.name)")])
+ ])
+ ]
+}
+
+func render(_ nodes: [Node]) -> String {
+ return nodes.map(render).joined()
+}
+
+print(render(adminDetail(user: User(name: "Blob Jr.", isAdmin: false))))
+
+print(render(adminDetail(user: User(name: "Blob Sr.", isAdmin: true))))
+
+func users(_ names: [String]) -> Node {
+ return ul(names.map(userItem))
+}
+
+func userItem(_ name: String) -> Node {
+ return li([.text(name)])
+}
+
+print(render(users(["Blob", "Blob Jr."])))
+
+let template1 = """
+
+ {% for user in users %}
+ {% include "userItem" user %}
+ {% endfor %}
+
+"""
+
+
+class MemoryTemplateLoader: Loader {
+ func loadTemplate(name: String, environment: Environment) throws -> Template {
+ if name == "userItem" {
+ return Template(templateString: """
+{{ user }}
+""", environment: environment)
+ }
+
+ throw TemplateDoesNotExist(templateNames: [name], loader: self)
+ }
+}
+
+let environment = Environment(loader: MemoryTemplateLoader())
+
+//print(try template1.render(["users": ["Blob", "Blob Jr."]]))
+
+print(
+try environment
+ .renderTemplate(
+ string: template1,
+ context: ["users": ["Blob", "Blob Jr."]]
+)
+)
+
+func redacted(_ node: Node) -> Node {
+ switch node {
+ case let .el("img", attrs, children):
+ return .el(
+ "img",
+ attrs.filter { attrName, _ in attrName != "src" }
+ + [("style", "background: black")],
+ children
+ )
+ case let .el(tag, attrs, children):
+ return .el(tag, attrs, children.map(redacted))
+ case let .text(string):
+ return .text(
+ string
+ .split(separator: " ")
+ .map { String.init(repeating: "â", count: $0.count )}
+ .joined(separator: " ")
+ )
+ }
+}
+
+
+import WebKit
+import PlaygroundSupport
+
+let doc = header([
+ h1(["Point-Free"]),
+ p([id("blurb")], [
+ "Functional programming in Swift. ",
+ a([href("/about")], ["Learn more"]),
+ "!"
+ ]),
+ img([src("https://pbs.twimg.com/profile_images/907799692339269634/wQEf0_2N_400x400.jpg"), width(64), height(64)])
+ ])
+
+let webView = WKWebView(frame: .init(x: 0, y: 0, width: 360, height: 480))
+webView.loadHTMLString(render(redacted(doc)), baseURL: nil)
+PlaygroundPage.current.liveView = webView
+
+
+print(render(redacted(doc)))
diff --git a/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift b/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift
new file mode 100644
index 00000000..8ad14e5f
--- /dev/null
+++ b/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift
@@ -0,0 +1,38 @@
+/*:
+ # DSLs vs. Templating Languages
+
+ ## Exercises
+
+ 1.) In this episode we expressed a lot of HTML âviewsâ as just plain functions from some data type into the Node type. In past episodes we saw that functions `(A) -> B` have both a `map` and `contramap` defined, the former corresponding to post-composition and the latter pre-composition. What does `map` and `contramap` represent in the context of an HTML view `(A) -> Node`?
+ */
+// TODO
+/*:
+ 2.) When building a website you often realize that you want to be able to reuse an outer âshellâ of a view, and plug smaller views into it. For example, the header, nav and footer would consist of the âshellâ, and then the content of your homepage, about page, contact page, etc. make up the inside. This is a kind of âview compositionâ, and most templating languages provide something like it (Rails calls it layouts, Stencil calls it inheritance).
+
+ Formulate what this form of view composition looks like when you think of views as just functions of the form `(A) -> Node`.
+ */
+// TODO
+/*:
+ 3.) In previous episodes on this series we have discussed the `<>` (diamond) operator. We have remarked that this operator comes up anytime we have a nice way of combining two values of the same type together into a third value of the same type, i.e. functions of the form `(A, A) -> A`.
+
+ Given two views of the form `v, w: (A) -> [Node]`, it is possible to combine them into one view. Define the diamond operator that performs this operation: `<>: ((A) -> [Node], (A) -> [Node]) -> (A) -> [Node]`.
+ */
+// TODO
+/*:
+ 4.) Right now any node is allowed to be embedded inside any other node, even though certain HTML semantics forbid that. For example, the list item tag `` is only allowed to be embedded in unordered lists `` and ordered lists ``. We canât enforce this property through the `Node` type, but we can do it through the functions we define for constructing tags. The technique uses something known as phantom types, and itâs similar to what we did in our Tagged episode. Here is a series of exercises to show how it works:
+
+ 4a.) First define a new `ChildOf` type. Itâs a struct that simply wraps a `Node` value, but most importantly it has a generic ``. We will use this generic to control when certain nodes are allowed to be embedded inside other nodes.
+ */
+// TODO
+/*:
+ 4b.) Define two new types, `Ol` and `Ul`, that will act as the phantom types for `ChildOf`. Since we do not care about the contents of these types, they can just be simple empty enums.
+ */
+// TODO
+/*:
+ 4c.) Define a new protocol, `ContainsLi`, and make both `Ol` and `Ul` conform to it. Again, we donât care about the contents of this protocol, it is only a means to tag `Ol` and `Ul` as having the property that they are allowed to contain `- ` elements.
+ */
+// TODO
+/*:
+ 4d.) Finally, define three new tag functions `ol`, `ul` and `li` that allow you to nest `
- `s inside `
`s and ``s but prohibit you from putting `li`s in any other tags. You will need to use the types `ChildOf`, `ChildOf` and `ContainsLi` to accomplish this.
+ */
+// TODO
diff --git a/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Sources/Util.swift b/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Sources/Util.swift
new file mode 100644
index 00000000..60619734
--- /dev/null
+++ b/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Sources/Util.swift
@@ -0,0 +1,61 @@
+
+public enum Node {
+ indirect case el(String, [(String, String)], [Node])
+ case text(String)
+}
+
+extension Node: ExpressibleByStringLiteral {
+ public init(stringLiteral value: String) {
+ self = .text(value)
+ }
+}
+public func header(_ children: [Node]) -> Node {
+ return .el("header", [], children)
+}
+public func h1(_ children: [Node]) -> Node {
+ return .el("h1", [], children)
+}
+public func ul(_ children: [Node]) -> Node {
+ return .el("ul", [], children)
+}
+public func li(_ children: [Node]) -> Node {
+ return .el("li", [], children)
+}
+public func p(_ attrs: [(String, String)], _ children: [Node]) -> Node {
+ return .el("p", attrs, children)
+}
+public func a(_ attrs: [(String, String)], _ children: [Node]) -> Node {
+ return .el("a", attrs, children)
+}
+public func img(_ attrs: [(String, String)]) -> Node {
+ return .el("img", attrs, [])
+}
+
+public func id(_ value: String) -> (String, String) {
+ return ("id", value)
+}
+public func href(_ value: String) -> (String, String) {
+ return ("href", value)
+}
+public func src(_ value: String) -> (String, String) {
+ return ("src", value)
+}
+public func width(_ value: Int) -> (String, String) {
+ return ("width", "\(value)")
+}
+public func height(_ value: Int) -> (String, String) {
+ return ("height", "\(value)")
+}
+
+public func render(_ node: Node) -> String {
+ switch node {
+ case let .el(tag, attrs, children):
+ let formattedAttrs = attrs
+ .map { key, value in "\(key)=\"\(value)\"" }
+ .joined(separator: " ")
+ let formattedChildren = children.map(render).joined()
+ return "<\(tag) \(formattedAttrs)>\(formattedChildren)\(tag)>"
+ case let .text(string):
+ return string
+ }
+}
diff --git a/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/contents.xcplayground b/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/contents.xcplayground
new file mode 100644
index 00000000..f6677ad0
--- /dev/null
+++ b/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/contents.xcplayground
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/0029-dsls-vs-templating-languages/TemplatingLanguages.xcworkspace/contents.xcworkspacedata b/0029-dsls-vs-templating-languages/TemplatingLanguages.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..a10a73e7
--- /dev/null
+++ b/0029-dsls-vs-templating-languages/TemplatingLanguages.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/0029-dsls-vs-templating-languages/TemplatingLanguages.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/0029-dsls-vs-templating-languages/TemplatingLanguages.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/0029-dsls-vs-templating-languages/TemplatingLanguages.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/README.md b/README.md
index 91d3d25c..a0bdabce 100644
--- a/README.md
+++ b/README.md
@@ -31,3 +31,4 @@ This repository is the home of code written on episodes of
1. [Domain Specific Languages: Part 1](0026-edsls-pt1)
1. [Domain Specific Languages: Part 2](0027-edsls-pt2)
1. [An HTML DSL](0028-html-dsl)
+1. [DSLs vs. Templating Languages](0029-dsls-vs-templating-languages)