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

Functional Swift

Functional Swift

Presented at 360 iDev, Denver

Chris Eidhof | @chriseidhof

August 25, 2014
Tweet

More Decks by Chris Eidhof | @chriseidhof

Other Decks in Technology

Transcript

  1. The Objective-C way CIFilter *hueAdjust = [CIFilter filterWithName:@"CIHueAdjust"]; [hueAdjust setDefaults];

    [hueAdjust setValue: myCIImage forKey: kCIInputImageKey]; [hueAdjust setValue: @2.094f forKey: kCIInputAngleKey];
  2. 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 } }
  3. Color Generator func colorGenerator(color: NSColor) -> Filter { return {

    _ in let filter = CIFilter(name:"CIConstantColorGenerator", parameters: [kCIInputColorKey: color]) return filter.outputImage } }
  4. 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()) } }
  5. Color Overlay func colorOverlay(color: NSColor) -> Filter { return {

    image in let overlay = colorGenerator(color)(image) return compositeSourceOver(overlay)(image) } }
  6. Combining everything let blurRadius = 5.0 let overlayColor = NSColor.redColor().colorWithAlphaComponent(0.2)

    let blurredImage = blur(blurRadius)(image) let overlaidImage = colorOverlay(overlayColor)(blurredImage)
  7. Filter composition with an operator infix operator |> { associativity

    left } func |> (filter1: Filter, filter2: Filter) -> Filter { return {img in filter1(filter2(img))} }
  8. Function composition func |> (f1: B -> C, f2: A

    -> B) -> A -> C { return {x in f1(f2(x))} }
  9. 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))
  10. 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
  11. The data let summits = ["Elbert": 440, "Massive": 428, "Harvard":

    421, "La Plata": 368, "Blanca": 357] barGraph(summits.keysAndValues)
  12. 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 }
  13. 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) }
  14. 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)
  15. 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)
  16. 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)
  17. Drawing Aligned Diagrams case .Align(let vec, let d): let diagram

    = d.diagram() let frame = fit(vec, diagram.size, bounds) draw(context, frame, diagram) } }
  18. 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) }
  19. { "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\/" } ] } }
  20. The Blog struct struct Blog { let id: Int let

    name: String let needsPassword : Bool let url: NSURL }
  21. 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 }
  22. Parsing a string func string(input: [String:AnyObject], key: String) -> String?

    { let result = input[key] return result as String? }
  23. 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 }
  24. 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 } }
  25. 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 }
  26. Flattening func flatten<A,B,C,D>(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 }
  27. 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 }
  28. Removing the if-statement func apply<A, B, C, D, R>(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 }
  29. 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)) }
  30. Apply, but simpler func apply<A, R>(l: (A -> R)?, r:

    A?) -> R? { if let l1 = l { if let r1 = r { return l1(r1) } } return nil }
  31. Currying func curry<A,B,C,D,R>(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) } } } } }
  32. 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)
  33. 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)
  34. 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)
  35. 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) }
  36. Apply as an operator infix operator <*> { associativity left

    precedence 150 } func <*><A, B>(l: (A -> B)?, r: A?) -> B? { if let l1 = l { if let r1 = r { return l1(r1) } } return nil }
  37. 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 }
  38. 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 }
  39. 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) } }