dotswift 2016 : beyond crusty - real-world protocols

32
REAL-WORLD PROTOCOLS BEYOND CRUSTY

Upload: rob-napier

Post on 10-Jan-2017

11.998 views

Category:

Software


2 download

TRANSCRIPT

REAL-WORLD PROTOCOLSBEYOND CRUSTY

ANIMAL

DOG CAT

TIGER HOUSECATWOLF DOMESTIC

CHIHUAHUA GREAT DANE PERSIAN MANX

BIRD

protocol Animal { func eat(food: Food) }

protocol Animal { typealias Food func eat(food: Food) }

// An Animal can eat protocol Animal { typealias Food func eat(food: Food) }

struct Cow: Animal { func eat(food: Grass) { print("moo") } }

struct Sheep: Animal { func eat(food: Grass) { print("bah") } }

let grassEaters: [Animal] = [Cow(), Sheep()]

error: protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements

Cow: Animal eat()

AnyAnimal: Animal eat()

Cow: Animal eat()

let eat = Cow().eat

let eat = Cow().eat eat(Grass())

let eat = { Cow().eat($0) } eat(Grass())

struct AnyAnimal<Food>: Animal {

init<A: Animal where A.Food == Food>(_ animal: A) { _eat = animal.eat }

func eat(food: Food) { _eat(food) }

private let _eat: (Food) -> Void }

let grassEaters = [AnyAnimal(Cow()), AnyAnimal(Sheep())]

for eater in grassEaters { eater.eat(Grass()) }

protocol Animal { typealias Food func eat(food: Food) }

struct AnyAnimal<Food>: Animal { func eat(food: Food) { _eat(food) }

init ...

private let _eat: (Food) -> Void }

struct Animal<Food> { let eat: (Food) -> Void }

protocol Animal { typealias Food func eat(food: Food) }

let grassEaters = [ Animal(eat: cow.eat), // method Animal(eat: { _ in print("bah") } ) // function literal ]

let session = NSURLSession() session.delegate = self let task = session.dataTaskWithURL(url)

let task = session.dataTaskWithURL(url, completionHandler: { ... })

DELEGATES

HANDLERS

SOME RULES OF THUMB

‣ One method? Maybe function.

‣ Three or more methods? Protocol.

‣ Relationship short-lived? Function.

‣ Relationship ongoing? Probably protocol/delegate.

protocol P { typealias A typealias B

func x(a: A) -> B func y(a: A) -> B}

struct S<A,B> { func x(a: A) -> B {…} func y(a: A) -> B {…}}

func x<A,B>(a: A) -> B {…}

func y<A,B>(a: A) -> B {…}

Pick the form that works best for your problem.

THE GENERIC SOLVERPARSER SEARCHER OUTPUT

protocol Parsing { typealias Problem func parse(data: NSData) -> Problem }

protocol Searching { typealias Problem typealias Solution func search(problem: Problem) -> Solution }

protocol Outputting { typealias Solution func output(solution: Solution) }

struct Solver< Parser: Parsing, Searcher: Searching, Outputter: Outputting where Parser.Problem == Searcher.Problem, Searcher.Solution == Outputter.Solution >{ let parser: Parser let searcher: Searcher let outputter: Outputter

func solve(data: NSData) { outputter.output(searcher.search(parser.parse(data))) } }

protocol Solving { typealias Parser: Parsing typealias Searcher: Searching typealias Outputter: Outputting

var parser: Parser { get } var searcher: Searcher { get } var outputter: Outputter { get } }

extension Solving where Parser.Problem == Searcher.Problem, Searcher.Solution == Outputter.Solution { func solve(data: NSData) { outputter.output(searcher.search(parser.parse(data))) } }

struct SimpleSolver< Parser: Parsing, Searcher: Searching, Outputter: Outputting where Parser.Problem == Searcher.Problem, Searcher.Solution == Outputter.Solution >: Solving { let parser: Parser let searcher: Searcher let outputter: Outputter // ... }

All the code repetition was making him ornery, and if Crusty ain't happy, ain't nobody happy.

Protocol-Oriented Programming in Swift

protocol Solving { typealias Problem typealias Solution

func parse(data: NSData) -> Problem func search(problem: Problem) -> Solution func output(solution: Solution) -> Void }

extension Solving { func solve(data: NSData) { output(search(parse(data))) } }

struct SimpleSolver<Problem, Solution>: Solving { let _parse: (NSData) -> Problem let _search: (Problem) -> Solution let _output: (Solution) -> Void

init( parse: (NSData) -> Problem, search: (Problem) -> Solution, output: (Solution) -> Void) { _parse = parse _search = search _output = output }

func parse(data: NSData) -> Problem { return _parse(data) } func search(problem: Problem) -> Solution { return _search(problem) } func output(solution: Solution) { _output(solution) } //... }

protocol Solving { typealias Problem typealias Solution

var parse: (NSData) -> Problem { get } var search: (Problem) -> Solution { get } var output: (Solution) -> Void { get } }

extension Solving { func solve(data: NSData) { output(search(parse(data))) } }

struct SimpleSolver<Problem, Solution>: Solving { let parse: (NSData) -> Problem let search: (Problem) -> Solution let output: (Solution) -> Void // ... }

ENCAPSULATE WHAT VARIES

SimpleSolver

ParserSearcherOutputter

“Simple” functions

FancySolver

ParserSearcherOutputter

“Fancy” functions“Simple” properties “Fancy” properties

SolvingParserSearcherOutputter

SimpleSolver

“Simple” functions

FancySolver

“Fancy” functions“Simple” properties “Fancy” properties

SolverConfigParserSearcherOutputter

Config Config

struct SolverConfig<Problem, Solution> { let parse: (NSData) -> Problem let search: (Problem) -> Solution let output: (Solution) -> Void

func solve(data: NSData) { output(search(parse(data))) } }

struct SimpleSolver<Problem, Solution> { let config: SolverConfig<Problem, Solution> // ... }

PARAMETERIZE ON DATA TYPES, NOT HANDLER TYPES

protocol Parsing { typealias Problem func parse(data: NSData) -> Problem }

protocol Searching { typealias Problem typealias Solution func search(problem: Problem) -> Solution }

protocol Outputting { typealias Solution func output(solution: Solution) }

protocol Solving { typealias Parser: Parsing typealias Searcher: Searching typealias Outputter: Outputting

var parser: Parser { get } var searcher: Searcher { get } var outputter: Outputter { get } }

extension Solving where Parser.Problem == Searcher.Problem, Searcher.Solution == Outputter.Solution { func solve(data: NSData) { outputter.output(searcher.search(parser.parse(data))) } }

struct SimpleSolver< Parser: Parsing, Searcher: Searching, Outputter: Outputting where Parser.Problem == Searcher.Problem, Searcher.Solution == Outputter.Solution >: Solving { let parser: Parser let searcher: Searcher let outputter: Outputter // ... }

struct SolverConfig<Problem, Solution> { let parse: (NSData) -> Problem let search: (Problem) -> Solution let output: (Solution) -> Void

func solve(data: NSData) { output(search(parse(data))) } }

struct SimpleSolver<Problem, Solution> { let config: SolverConfig<Problem, Solution> // ... }

Protocols ⬌ Structs ⬌ Functions

Encapsulate What Varies

Parameterize on Data, not Handlers

REAL-WORLD PROTOCOLSBEYOND CRUSTY

Rob Napierrobnapier.net@[email protected]