Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[iOS Conf SG 2016] A Toy Web Framework in Swift

[iOS Conf SG 2016] A Toy Web Framework in Swift

A talk using various parts of a web framework implementation to talk about a few Swift features — what they can do, and what they can't do (yet).

Hwee-Boon Yar

October 21, 2016
Tweet

More Decks by Hwee-Boon Yar

Other Decks in Programming

Transcript

  1. A Toy Web Framework in Swift Boon

  2. Why a Web Framework? • I've never built one •

    Well-known domain • The basic premise is simple
  3. What is a Web (Application) Framework? • Routing • Presentation

    • Logic • Persistence • Testing
  4. Blamework

  5. • Routing • Presentation • Logic • Persistence • Testing

  6. None
  7. None
  8. Prototype with Swift Playgrounds

  9. None
  10. GCDWebServer

  11. 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)
  12. None
  13. 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
  14. Vapor drop.get("welcome") { request in return "Hello" } — h$ps://vapor.github.io/documenta8on/rou8ng/basic.html

  15. 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
  16. Routing let routes: [String: Component] = "/something": someComponent, "/anotherPath": anotherComponent,

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

  18. Presentation

  19. None
  20. None
  21. 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) } } }
  22. A domain-specific language (DSL) is a computer language specialized to

    a particular application domain. — h$ps://en.wikipedia.org/wiki/Domain-specific_language
  23. Swift vs. Ruby

  24. 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) } } }
  25. @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) }
  26. @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("</\(name)>") return w.toString() }
  27. @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("</\(name)>") return w.toString() }
  28. @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) }
  29. map([!, ", #], cook) => [$, %, &] filter([$, %,

    &], isVegetarian) => [$, &] reduce([$, &], eat) => ' — h$ps://twi$er.com/steveluscher/status/741089564329054208
  30. Persistence

  31. Prevayler

  32. Premise 1: Structs

  33. Premise 2: Protocols

  34. Premise 3: Minimal code

  35. Only collections and primitive types

  36. struct Address { var line1: String var line2: String var

    zipCode: Int }
  37. 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")
  38. struct Address { var line1: String var line2: String var

    zipCode: Int func toDictionary() -> [String:Any] { return ["line1": line1, "line2": line2, "zipCode: zipCode"] } }
  39. Protocols + Default Implementations protocol PersistentObject { func toDictionary() ->

    [String: Any] }
  40. 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 {//...} }
  41. 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 } }
  42. 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) } }
  43. struct Address { var line1: String var line2: String var

    zipCode: Int }
  44. 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")
  45. struct Address: PersistentObject { var line1: String var line2: String

    var zipCode: Int }
  46. print(items) [Item(title: "Item 1", age: 10, address: Address(value: "address 1")),

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

    "Item 2", age: 10, address: Address(value: "address 1"))]
  48. 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"
  49. 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 } }
  50. for case let (label?, value) in m.children { result[label] =

    gen(value: value) }
  51. 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)
  52. 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
  53. let l2 = [[1, 2], [3, 4, 5]].flatMap {$0} l2

    // == [1, 2, 3, 4, 5]
  54. 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
  55. Javascript

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

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

  58. More

  59. 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) } }
  60. extension Collection where Iterator.Element: PersistentObject { } extension Collection where

    Iterator.Element == PersistentObject { }
  61. 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^
  62. Reflection Smoke + Mirrors extension PersistentObject { func toDictionary() ->

    [String:Any] {} //... } extension Array where Element: PersistentObject {//...} extension Dictionary where Value: PersistentObject {//...}
  63. Pattern Matching

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

    21, ...
  65. 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]
  66. //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 } }
  67. #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
  68. Swift is a Young Language

  69. • 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
  70. Thank You Follow @hboon