Slide 1

Slide 1 text

Functional Swift @chriseidhof

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Core Image

Slide 4

Slide 4 text

The Objective-C way CIFilter *hueAdjust = [CIFilter filterWithName:@"CIHueAdjust"]; [hueAdjust setDefaults]; [hueAdjust setValue: myCIImage forKey: kCIInputImageKey]; [hueAdjust setValue: @2.094f forKey: kCIInputAngleKey];

Slide 5

Slide 5 text

A Swift Filter typealias Filter = CIImage -> CIImage

Slide 6

Slide 6 text

Blur func blur(radius: Double) -> Filter { return { image in let parameters : Parameters = [kCIInputRadiusKey: radius, kCIInputImageKey: image] let filter = CIFilter(name:"CIGaussianBlur", parameters:parameters) return filter.outputImage } }

Slide 7

Slide 7 text

Example let url = NSURL(string: "http://tinyurl.com/sfswift"); let image = CIImage(contentsOfURL: url) let blurBy5 = blur(5) let blurred = blurBy5(image)

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Color Generator func colorGenerator(color: NSColor) -> Filter { return { _ in let filter = CIFilter(name:"CIConstantColorGenerator", parameters: [kCIInputColorKey: color]) return filter.outputImage } }

Slide 10

Slide 10 text

Composite Source Over func compositeSourceOver(overlay: CIImage) -> Filter { return { image in let parameters : Parameters = [kCIInputBackgroundImageKey: image, kCIInputImageKey: overlay] let filter = CIFilter(name:"CISourceOverCompositing", parameters: parameters) return filter.outputImage.imageByCroppingToRect(image.extent()) } }

Slide 11

Slide 11 text

Color Overlay func colorOverlay(color: NSColor) -> Filter { return { image in let overlay = colorGenerator(color)(image) return compositeSourceOver(overlay)(image) } }

Slide 12

Slide 12 text

Combining everything let blurRadius = 5.0 let overlayColor = NSColor.redColor().colorWithAlphaComponent(0.2) let blurredImage = blur(blurRadius)(image) let overlaidImage = colorOverlay(overlayColor)(blurredImage)

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Combining everything, take 2 let result = colorOverlay(overlayColor)(blur(blurRadius)(image))

Slide 15

Slide 15 text

Filter composition func composeFilters(filter1: Filter, filter2: Filter) -> Filter { return {img in filter1(filter2(img)) } }

Slide 16

Slide 16 text

Using filter composition let myFilter1 = composeFilters(blur(blurRadius), colorOverlay(overlayColor)) let result1 = myFilter1(image)

Slide 17

Slide 17 text

Filter composition with an operator infix operator |> { associativity left } func |> (filter1: Filter, filter2: Filter) -> Filter { return {img in filter1(filter2(img))} }

Slide 18

Slide 18 text

Using filter composition let myFilter2 = blur(blurRadius) |> colorOverlay(overlayColor) let result2 = myFilter2(image)

Slide 19

Slide 19 text

Function composition func |> (f1: B -> C, f2: A -> B) -> A -> C { return {x in f1(f2(x))} }

Slide 20

Slide 20 text

Diagrams

Slide 21

Slide 21 text

An example

Slide 22

Slide 22 text

Objective-C [[NSColor blueColor] setFill] CGContextFillRect(context, CGRectMake(0.0,37.5,75.0,75.0)) [[NSColor redColor] setFill] CGContextFillRect(context, CGRectMake(75.0,0.0,150.0,150.0)) [[NSColor greenColor] setFill] CGContextFillEllipseInRect(context, CGRectMake(225.0,37.5,75.0,75.0))

Slide 23

Slide 23 text

Another example

Slide 24

Slide 24 text

Swift let blueSquare = square(side: 1).fill(NSColor.blueColor()) let redSquare = square(side: 2).fill(NSColor.redColor()) let greenCircle = circle(radius: 1).fill(NSColor.greenColor()) let example1 = blueSquare ||| redSquare ||| greenCircle

Slide 25

Slide 25 text

Extending let example2 = blueSquare ||| circle(radius: 1).fill(NSColor.cyanColor()) ||| redSquare ||| greenCircle

Slide 26

Slide 26 text

A bar graph La Plata Blanca Elbert Massive Harvard

Slide 27

Slide 27 text

The data let summits = ["Elbert": 440, "Massive": 428, "Harvard": 421, "La Plata": 368, "Blanca": 357] barGraph(summits.keysAndValues)

Slide 28

Slide 28 text

Drawing a bar graph func barGraph(input: [(String,Double)]) -> Diagram { let values : [CGFloat] = input.map { CGFloat($0.1) } let bars = hcat(normalize(values).map { return rect(width: 1, height: 3*x). alignBottom() }) let labels = hcat(input.map { x in return text(width: 1, height: 0.3, text: x.0). alignTop() }) return bars --- labels }

Slide 29

Slide 29 text

Primitives enum Primitive { case Ellipsis case Rectangle case Text(String) }

Slide 30

Slide 30 text

The Diagram type enum Diagram { case Prim(CGSize, Primitive) case Beside(Diagram, Diagram) case Below(Diagram, Diagram) case Attributed(Attribute, Diagram) case Align(Vector2D, Diagram) }

Slide 31

Slide 31 text

Drawing func draw(context: CGContextRef, bounds: CGRect, diagram: Diagram) { switch diagram { case .Prim(let size, .Ellipsis): let frame = fit(center, size, bounds) CGContextFillEllipseInRect(context, frame) case .Prim(let size, .Rectangle): let frame = fit(center, size, bounds) CGContextFillRect(context, frame)

Slide 32

Slide 32 text

Drawing Text case .Prim(let size, .Text(let text)): let frame = fit(center, size, bounds) let attributes = [NSFontAttributeName: NSFont.systemFontOfSize(12)] let attributedText = NSAttributedString (string: text, attributes: attributes) attributedText.drawInRect(frame)

Slide 33

Slide 33 text

Drawing Attributes case .Attributed(.FillColor(let color), let d): CGContextSaveGState(context) color.set() draw(context, bounds, d) CGContextRestoreGState(context)

Slide 34

Slide 34 text

Drawing Combinations case .Beside(let l, let r): let (lFrame, rFrame) = splitHorizontal(bounds, l.size/diagram.size) draw(context, lFrame, l) draw(context, rFrame, r) case .Below(let t, let b): let (lFrame, rFrame) = splitVertical(bounds, b.size/diagram.size) draw(context, lFrame, b) draw(context, rFrame, t)

Slide 35

Slide 35 text

Drawing Aligned Diagrams case .Align(let vec, let d): let diagram = d.diagram() let frame = fit(vec, diagram.size, bounds) draw(context, frame, diagram) } }

Slide 36

Slide 36 text

Operators infix operator ||| { associativity left } func ||| (l: Diagram, r: Diagram) -> Diagram { return Diagram.Beside(l, r) } infix operator --- { associativity left } func --- (l: Diagram, r: Diagram) -> Diagram { return Diagram.Below(l, r) }

Slide 37

Slide 37 text

JSON

Slide 38

Slide 38 text

{ "stat" : "ok", "blogs" : { "blog" : [ { "needspassword" : true, "id" : 73, "name" : "Bloxus test", "url" : "http:\/\/remote.bloxus.com\/" }, { "id" : 74, "name" : "Manila Test", "needspassword" : false, "url" : "http:\/\/flickrtest1.userland.com\/" } ] } }

Slide 39

Slide 39 text

The Blog struct struct Blog { let id: Int let name: String let needsPassword : Bool let url: NSURL }

Slide 40

Slide 40 text

A first version func parseBlog(blogDict: [String:AnyObject]) -> Blog? { if let id = blogDict["id"] as NSNumber? { if let name = blogDict["name"] as NSString? { if let needsPassword = blogDict["needspassword"] as NSNumber? { if let url = blogDict["url"] as NSString? { return Blog(id: id.integerValue, name: name, needsPassword: needsPassword.boolValue, url: NSURL(string: url) ) } } } } return nil }

Slide 41

Slide 41 text

Parsing a string func string(input: [String:AnyObject], key: String) -> String? { let result = input[key] return result as String? }

Slide 42

Slide 42 text

Version 2 func parseBlog(blogDict: [String:AnyObject]) -> Blog? { if let id = blogDict["id"] as NSNumber? { if let name = string(blogDict, "name") { if let needsPassword = blogDict["needspassword"] as NSNumber? { if let url = string(blogDict, "url") { return Blog(id: id.integerValue, name: name, needsPassword: needsPassword.boolValue, url: NSURL(string: url) ) } } } } return nil }

Slide 43

Slide 43 text

Parsing Numbers func number(input: [NSObject:AnyObject], key: String) -> NSNumber? { let result = input[key] return result as NSNumber? } func int(input: [NSObject:AnyObject], key: String) -> Int? { return number(input,key).map { $0.integerValue } } func bool(input: [NSObject:AnyObject], key: String) -> Bool? { return number(input,key).map { $0.boolValue } }

Slide 44

Slide 44 text

Version 3 func parseBlog(blogDict: [String:AnyObject]) -> Blog? { if let id = int(blogDict, "id") { if let name = string(blogDict, "name") { if let needsPassword = bool(blogDict, "needspassword") { if let url = string(blogDict, "url") { return Blog(id: id, name: name, needsPassword: needsPassword, url: NSURL(string: url) ) } } } } return nil }

Slide 45

Slide 45 text

Flattening func flatten(oa: A?,ob: B?,oc: C?,od: D?) -> (A,B,C,D)? { if let a = oa { if let b = ob { if let c = oc { if let d = od { return (a,b,c,d) } } } } return nil }

Slide 46

Slide 46 text

Version 4 func parseBlog(blogData: [String:AnyObject]) -> Blog? { let id = int(blogData,"id") let name = string(blogData,"name") let needsPassword = bool(blogData,"needspassword") let url = string(blogData,"url").map { NSURL(string:$0) } if let (id, name, needsPassword, url) = flatten(id, name, needsPassword, url) { return Blog(id: id, name: name, needsPassword: needsPassword, url: url) } return nil }

Slide 47

Slide 47 text

Removing the if-statement func apply(l: ((A,B,C,D) -> R)?, r: (A,B,C,D)?) -> R? { if let l1 = l { if let r1 = r { return l1(r1) } } return nil }

Slide 48

Slide 48 text

Version 5 func parse(blogData: [String:AnyObject]) -> Blog? { let id = int(blogData,"id") let name = string(blogData,"name") let needsPassword = bool(blogData,"needspassword") let url = string(blogData,"url").map { NSURL(string:$0) } let makeBlog = { Blog(id: $0, name: $1, needsPassword: $2, url: $3) } return apply(makeBlog, flatten(id, name, needsPassword, url)) }

Slide 49

Slide 49 text

Apply, but simpler func apply(l: (A -> R)?, r: A?) -> R? { if let l1 = l { if let r1 = r { return l1(r1) } } return nil }

Slide 50

Slide 50 text

Currying func curry(f: (A,B,C,D) -> R) -> A -> B -> C -> D -> R { return { a in { b in { c in { d in f(a,b,c,d) } } } } }

Slide 51

Slide 51 text

Currying by example // Has type: (Int, String, Bool, NSURL) -> Blog let blog = { Blog(id: $0, name: $1, needsPassword: $2, url: $3) } // Has type: Int -> (String -> (Bool -> (NSURL -> Blog))) let makeBlog = curry(blog) // Or: Int -> String -> Bool -> NSURL -> Blog let makeBlog = curry(blog)

Slide 52

Slide 52 text

Currying some more // Has type: Int -> String -> Bool -> NSURL -> Blog let makeBlog = curry(blog) // Has type: Int? let id = int(blogData, "id") // Has type: (String -> Bool -> NSURL -> Blog)? let step1 = apply(makeBlog,id)

Slide 53

Slide 53 text

Currying some more // Has type: (String -> Bool -> NSURL -> Blog)? let step1 = apply(makeBlog,id) // Has type: String? let name = string(blogData,"name") // Has type: (Bool -> NSURL -> Blog)? let step2 = apply(step1,name)

Slide 54

Slide 54 text

Version 6 func parse(blogData: [String:AnyObject]) -> Blog? { let id = int(blogData,"id") let name = string(blogData,"name") let needsPassword = bool(blogData,"needspassword") let url = string(blogData,"url").map { NSURL(string:$0) } let makeBlog = curry { Blog(id: $0, name: $1, needsPassword: $2, url: $3) } return apply(apply(apply(apply(makeBlog, id), name), needsPassword), url) }

Slide 55

Slide 55 text

Apply as an operator infix operator <*> { associativity left precedence 150 } func <*>(l: (A -> B)?, r: A?) -> B? { if let l1 = l { if let r1 = r { return l1(r1) } } return nil }

Slide 56

Slide 56 text

Version 7 func parse(blogData: [String:AnyObject]) -> Blog? { let id = int(blogData,"id") let name = string(blogData,"name") let needsPassword = bool(blogData,"needspassword") let url = string(blogData,"url").map { NSURL(string:$0) } let makeBlog = curry { Blog(id: $0, name: $1, needsPassword: $2, url: $3) } return makeBlog <*> id <*> name <*> needsPassword <*> url }

Slide 57

Slide 57 text

Flashback func parseBlog(blogDict: [String:AnyObject]) -> Blog? { if let id = blogDict["id"] as NSNumber? { if let name = blogDict["name"] as NSString? { if let needsPassword = blogDict["needspassword"] as NSNumber? { if let url = blogDict["url"] as NSString? { return Blog(id: id.integerValue, name: name, needsPassword: needsPassword.boolValue, url: NSURL(string: url) ) } } } } return nil }

Slide 58

Slide 58 text

Version 8 func parse(blogData: [String:AnyObject]) -> Blog? { let makeBlog = curry { Blog(id: $0, name: $1, needsPassword: $2, url: $3) } return makeBlog <*> int(blogData,"id") <*> string(blogData,"name") <*> bool(blogData,"needspassword") <*> string(blogData,"url").map { NSURL(string:$0) } }

Slide 59

Slide 59 text

...

Slide 60

Slide 60 text

https://speakerdeck.com/chriseidhof/

Slide 61

Slide 61 text

By Chris Eidhof, Florian Kugler and Wouter Swierstra

Slide 62

Slide 62 text

@chriseidhof