Pro Yearly is on sale from $80 to $50! »

Functional Swift

Functional Swift

Presented at 360 iDev, Denver

Ade0c334ecff1448bb96f5f733bf1f83?s=128

Chris Eidhof | @chriseidhof

August 25, 2014
Tweet

Transcript

  1. Functional Swift @chriseidhof

  2. None
  3. Core Image

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

    [hueAdjust setValue: myCIImage forKey: kCIInputImageKey]; [hueAdjust setValue: @2.094f forKey: kCIInputAngleKey];
  5. A Swift Filter typealias Filter = CIImage -> CIImage

  6. 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 } }
  7. Example let url = NSURL(string: "http://tinyurl.com/360image"); let image = CIImage(contentsOfURL:

    url) let blurBy5 = blur(5) let blurred = blurBy5(image)
  8. None
  9. Color Generator func colorGenerator(color: NSColor) -> Filter { return {

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

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

    let blurredImage = blur(blurRadius)(image) let overlaidImage = colorOverlay(overlayColor)(blurredImage)
  13. None
  14. Combining everything, take 2 let result = colorOverlay(overlayColor)(blur(blurRadius)(image))

  15. Filter composition func composeFilters(filter1: Filter, filter2: Filter) -> Filter {

    return {img in filter1(filter2(img)) } }
  16. Using filter composition let myFilter1 = composeFilters(blur(blurRadius), colorOverlay(overlayColor)) let result1

    = myFilter1(image)
  17. Filter composition with an operator infix operator |> { associativity

    left } func |> (filter1: Filter, filter2: Filter) -> Filter { return {img in filter1(filter2(img))} }
  18. Using filter composition let myFilter2 = blur(blurRadius) |> colorOverlay(overlayColor) let

    result2 = myFilter2(image)
  19. Function composition func |> (f1: B -> C, f2: A

    -> B) -> A -> C { return {x in f1(f2(x))} }
  20. Diagrams

  21. An example

  22. 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))
  23. Another example

  24. 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
  25. Extending let example2 = blueSquare ||| circle(radius: 1).fill(NSColor.cyanColor()) ||| redSquare

    ||| greenCircle
  26. A bar graph La Plata Blanca Elbert Massive Harvard

  27. The data let summits = ["Elbert": 440, "Massive": 428, "Harvard":

    421, "La Plata": 368, "Blanca": 357] barGraph(summits.keysAndValues)
  28. 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 }
  29. Primitives enum Primitive { case Ellipsis case Rectangle case Text(String)

    }
  30. 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) }
  31. 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)
  32. 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)
  33. Drawing Attributes case .Attributed(.FillColor(let color), let d): CGContextSaveGState(context) color.set() draw(context,

    bounds, d) CGContextRestoreGState(context)
  34. 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)
  35. Drawing Aligned Diagrams case .Align(let vec, let d): let diagram

    = d.diagram() let frame = fit(vec, diagram.size, bounds) draw(context, frame, diagram) } }
  36. 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) }
  37. JSON

  38. { "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\/" } ] } }
  39. The Blog struct struct Blog { let id: Int let

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

    { let result = input[key] return result as String? }
  42. 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 }
  43. 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 } }
  44. 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 }
  45. 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 }
  46. 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 }
  47. 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 }
  48. 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)) }
  49. 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 }
  50. 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) } } } } }
  51. 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)
  52. 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)
  53. 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)
  54. 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) }
  55. 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 }
  56. 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 }
  57. 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 }
  58. 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) } }
  59. Right.

  60. By Chris Eidhof, Florian Kugler and Wouter Swierstra

  61. @chriseidhof