Slide 1

Slide 1 text

A Toy Web Framework in Swift Boon

Slide 2

Slide 2 text

Why a Web Framework? • I've never built one • Well-known domain • The basic premise is simple

Slide 3

Slide 3 text

What is a Web (Application) Framework? • Routing • Presentation • Logic • Persistence • Testing

Slide 4

Slide 4 text

Blamework

Slide 5

Slide 5 text

• Routing • Presentation • Logic • Persistence • Testing

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Prototype with Swift Playgrounds

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

GCDWebServer

Slide 11

Slide 11 text

Wiring up GCDWebServer let response = appServer.process(request: Request(verb: req.method!, path: req.path, query: req.query, form: req.arguments)) return GCDWebServerDataResponse(html: response)

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

Routing get '/hobbies' do 'Return the HTML for a list of hobbies here' end post '/hobbies/new' do #Create a new hobby and redirect to GET end

Slide 14

Slide 14 text

Vapor drop.get("welcome") { request in return "Hello" } — h$ps://vapor.github.io/documenta8on/rou8ng/basic.html

Slide 15

Slide 15 text

Perfect api2Routes.add(method: .get, uri: "/call2", handler: { _, response in response.setBody(string: "API v2 CALL 2") response.completed() }) — h$ps://github.com/PerfectlySo9/PerfectExample-URLRouAng

Slide 16

Slide 16 text

Routing let routes: [String: Component] = "/something": someComponent, "/anotherPath": anotherComponent, "*": mainComponent] let appServer = BlameworkAppServer(routes: routes)

Slide 17

Slide 17 text

Routing let component = routes[request.path] ?? routes["*"]! return component.renderFor(request: request)

Slide 18

Slide 18 text

Presentation

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

html.head { html.title("Blamework demo") } html.body { html in html.p("My TODO list for today:") html.form { html in html.textField("item") html.submit("Add Item") } html.ol { html in for e in items { html.li(e) } } }

Slide 22

Slide 22 text

A domain-specific language (DSL) is a computer language specialized to a particular application domain. — h$ps://en.wikipedia.org/wiki/Domain-specific_language

Slide 23

Slide 23 text

Swift vs. Ruby

Slide 24

Slide 24 text

html.head { html.title("Blamework demo") } html.body { html in html.p("My TODO...:") html.form { html in html.textField("item") html.submit("Add Item") } html.ol { html in for e in items { html.li(e) } } }

Slide 25

Slide 25 text

@discardableResult public func body( _ string: String) -> String { return tag(name: "body", string: string) } @discardableResult public func body( block: (_ writer: HTMLWriter)->()) -> String { return tag(name: "body", block: block) }

Slide 26

Slide 26 text

@discardableResult public func tag( name: String, attributes: [String:String]=[String:String](), block: (_ writer: HTMLWriter)->()) -> String { let w = self.childWriter() let attr = attributesToString(attributes: attributes) w.name = "\(name)'s" w.string("<\(name) \(attr)>") w.string(block) w.string("") return w.toString() }

Slide 27

Slide 27 text

@discardableResult public func tag( name: String, attributes: [String:String]=[String:String](), string: String) -> String { let w = self.childWriter() let attr = attributesToString(attributes: attributes) w.name = "\(name)'s" w.string("<\(name) \(attr)>") w.string(string) w.string("") return w.toString() }

Slide 28

Slide 28 text

@discardableResult public func body( _ string: String) -> String { return tag(name: "body", string: string) } @discardableResult public func body( block: (_ writer: HTMLWriter)->()) -> String { return tag(name: "body", block: block) }

Slide 29

Slide 29 text

map([!, ", #], cook) => [$, %, &] filter([$, %, &], isVegetarian) => [$, &] reduce([$, &], eat) => ' — h$ps://twi$er.com/steveluscher/status/741089564329054208

Slide 30

Slide 30 text

Persistence

Slide 31

Slide 31 text

Prevayler

Slide 32

Slide 32 text

Premise 1: Structs

Slide 33

Slide 33 text

Premise 2: Protocols

Slide 34

Slide 34 text

Premise 3: Minimal code

Slide 35

Slide 35 text

Only collections and primitive types

Slide 36

Slide 36 text

struct Address { var line1: String var line2: String var zipCode: Int }

Slide 37

Slide 37 text

var addresses = [ Address(line1: "Block 123", line2: "Tampines Street 33", zipCode: "523123"), Address(line1: "20", line2: "Maryland Road", zipCode: "567800")] addresses.write(to: "/path/to/somewhere")

Slide 38

Slide 38 text

struct Address { var line1: String var line2: String var zipCode: Int func toDictionary() -> [String:Any] { return ["line1": line1, "line2": line2, "zipCode: zipCode"] } }

Slide 39

Slide 39 text

Protocols + Default Implementations protocol PersistentObject { func toDictionary() -> [String: Any] }

Slide 40

Slide 40 text

extension PersistentObject { func toDictionary() -> [String:Any] { let result = gen(value: self) if let result = result as? [String:Any] { return result } else { return [String:Any]() } } func gen(value: Any) -> Any {//...} }

Slide 41

Slide 41 text

Reflection + Mirror func gen(value: Any) -> Any { let m = Mirror(reflecting: value) if m.displayStyle == .struct { var result = [String:Any]() for case let (label?, value) in m.children { result[label] = gen(value: value) } return result } else { return value } }

Slide 42

Slide 42 text

extension Array where Element: PersistentObject { func write(to: String) -> Bool { let array = map {$0.toDictionary()} as NSArray return array.write(to: URL(fileURLWithPath: to), atomically: true) } } extension Dictionary where Value: PersistentObject { func write(to: String) -> Bool { var dict = [Key:[String:Any]]() for (k, v) in self { dict[k] = v.toDictionary() } let dict2 = dict as NSDictionary return dict2.write(to: URL(fileURLWithPath: to), atomically: true) } }

Slide 43

Slide 43 text

struct Address { var line1: String var line2: String var zipCode: Int }

Slide 44

Slide 44 text

var addresses = [ Address(line1: "Block 123", line2: "Tampines Street 33", zipCode: "523123"), Address(line1: "20", line2: "Maryland Road", zipCode: "567800")] addresses.write(to: "/path/to/somewhere")

Slide 45

Slide 45 text

struct Address: PersistentObject { var line1: String var line2: String var zipCode: Int }

Slide 46

Slide 46 text

print(items) [Item(title: "Item 1", age: 10, address: Address(value: "address 1")), Item(title: "Item 2", age: 10, address: Address(value: "address 1"))]

Slide 47

Slide 47 text

[Item(title: "Item 1", age: 10, address: Address(value: "address 1")), Item(title: "Item 2", age: 10, address: Address(value: "address 1"))]

Slide 48

Slide 48 text

dump(items) ▿ 2 elements ▿ Item - title: "Item 1" - age: 10 ▿ address: Address - value: "address 1" ▿ Item - title: "Item 2" - age: 10 ▿ address: Address - value: "address 1"

Slide 49

Slide 49 text

func gen(value: Any) -> Any { let m = Mirror(reflecting: value) if m.displayStyle == .struct { var result = [String:Any]() for case let (label?, value) in m.children { result[label] = gen(value: value) } return result } else { return value } }

Slide 50

Slide 50 text

for case let (label?, value) in m.children { result[label] = gen(value: value) }

Slide 51

Slide 51 text

var nums: [Int?] = [1, 2, 3, nil, 5] for e in nums { print("Value: ", e) } Value: Optional(1) Value: Optional(2) Value: Optional(3) Value: nil Value: Optional(5)

Slide 52

Slide 52 text

var nums: [Int?] = [1, 2, 3, nil, 5] for case let e? in nums { print("Value: ", e) } Value: 1 Value: 2 Value: 3 Value: 5

Slide 53

Slide 53 text

let l2 = [[1, 2], [3, 4, 5]].flatMap {$0} l2 // == [1, 2, 3, 4, 5]

Slide 54

Slide 54 text

var nums: [Int?] = [1, 2, 3, nil, 5] for case let e? in nums { print("Value: ", e) } for e in nums.flatMap({$0}) { print("Value: ", e) } Value: 1 Value: 2 Value: 3 Value: 5

Slide 55

Slide 55 text

Javascript

Slide 56

Slide 56 text

let routes: [String: Component] = ["/scripts/turbolinks.js": FileComponent(path: "/path/to/turbolinks.js"), //... ]

Slide 57

Slide 57 text

html.head { html in html.title("Blamework demo") html.script(src: "/scripts/turbolinks.js") }

Slide 58

Slide 58 text

More

Slide 59

Slide 59 text

extension Array where Element: PersistentObject { func write(to: String) -> Bool { let array = map {$0.toDictionary()} as NSArray return array.write(to: URL(fileURLWithPath: to), atomically: true) } }

Slide 60

Slide 60 text

extension Collection where Iterator.Element: PersistentObject { } extension Collection where Iterator.Element == PersistentObject { }

Slide 61

Slide 61 text

extension Collection where Iterator.Element: PersistentObject { } // var list: [PersistentObject] // this is NOT what I want ^ extension Collection where Iterator.Element == PersistentObject { } // var list: [SomeTypeThatImplementsPersistentObject] //e.g // var list: [Addresses] // this is what I want for PersistentObject^

Slide 62

Slide 62 text

Reflection Smoke + Mirrors extension PersistentObject { func toDictionary() -> [String:Any] {} //... } extension Array where Element: PersistentObject {//...} extension Dictionary where Value: PersistentObject {//...}

Slide 63

Slide 63 text

Pattern Matching

Slide 64

Slide 64 text

Fibonacci Sequence 0, 1, 1, 2, 3, 5, 8, 13, 21, ...

Slide 65

Slide 65 text

for e in 0...6 { print("Seq \(e): \(fib(e))") } Seq 0: [0] Seq 1: [0, 1] Seq 2: [0, 1, 1] Seq 3: [0, 1, 1, 2] Seq 4: [0, 1, 1, 2, 3] Seq 5: [0, 1, 1, 2, 3, 5] Seq 6: [0, 1, 1, 2, 3, 5, 8]

Slide 66

Slide 66 text

//0, 1, 1, 2, 3, 5, 8, 13, 21, ... func fib(_ i: Int) -> [Int] { if i == 0 { return [0] } else if i == 1 { return [0, 1] } else { let seq = fib(i-1) let c = seq.count let next = [seq[c-2] + seq[c-1]] return seq + next } }

Slide 67

Slide 67 text

#0, 1, 1, 2, 3, 5, 8, 13, 21, ... def fib(0) do [0] end def fib(1) do [1, 0] end def fib(n) do [x, y | rest] = fib(n-1) [x+y, x, y | rest] end

Slide 68

Slide 68 text

Swift is a Young Language

Slide 69

Slide 69 text

• GCDWebServer https://github.com/swisspol/GCDWebServer • Vapor https://vapor.github.io/documentation/routing/basic.html • Perfect https://github.com/PerfectlySoft/PerfectExample- URLRouting • Pharo/Smalltalk http://pharo.org • Seaside http://www.seaside.st • Elixir http://elixir-lang.org

Slide 70

Slide 70 text

Thank You Follow @hboon