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

Functional Swift - Brooklyn

Functional Swift - Brooklyn

Chris Eidhof | @chriseidhof

September 09, 2014
Tweet

More Decks by Chris Eidhof | @chriseidhof

Other Decks in Technology

Transcript

  1. Functional Swift
    @chriseidhof

    View Slide

  2. !

    View Slide

  3. https://twitter.com/markforrer/status/509407939107762176/photo/1

    View Slide

  4. View Slide

  5. Core Image

    View Slide

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

    View Slide

  7. A Swift Filter
    typealias Filter = CIImage -> CIImage

    View Slide

  8. 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
    }
    }

    View Slide

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

    View Slide

  10. View Slide

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

    View Slide

  12. 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())
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  15. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. Diagrams

    View Slide

  23. An example

    View Slide

  24. 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));

    View Slide

  25. Another example

    View Slide

  26. 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

    View Slide

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

    View Slide

  28. A bar graph
    La Plata Blanca Elbert Massive Harvard

    View Slide

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

    View Slide

  30. 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
    }

    View Slide

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

    View Slide

  32. 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)
    }

    View Slide

  33. 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)

    View Slide

  34. 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)

    View Slide

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

    View Slide

  36. 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)

    View Slide

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

    View Slide

  38. 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)
    }

    View Slide

  39. JSON

    View Slide

  40. {
    "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\/"
    }
    ]
    }
    }

    View Slide

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

    View Slide

  42. 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
    }

    View Slide

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

    View Slide

  44. 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
    }

    View Slide

  45. 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 }
    }

    View Slide

  46. 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
    }

    View Slide

  47. 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
    }

    View Slide

  48. 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
    }

    View Slide

  49. 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
    }

    View Slide

  50. 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))
    }

    View Slide

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

    View Slide

  52. 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) } } } }
    }

    View Slide

  53. 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)

    View Slide

  54. 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)

    View Slide

  55. 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)

    View Slide

  56. 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)
    }

    View Slide

  57. 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
    }

    View Slide

  58. 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
    }

    View Slide

  59. 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
    }

    View Slide

  60. 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)
    }
    }

    View Slide


  61. View Slide

  62. By Chris Eidhof, Florian Kugler and Wouter Swierstra

    View Slide

  63. @chriseidhof

    View Slide