Functional Programming in Swift

Video & transcription below provided by Realm: a replacement for SQLite & Core Data with first-class support for Swift!

Chris Eidhof is the author of the upcoming book Functional Programming in Swift, co-creator of objc.io and organizer of UIKonf. We were thrilled when he agreed to speak at the SLUG meetup in San Francisco. In this presentation, Chris spoke about how to leverage the capabilities of Swift to do functional programming, not as a replacement for OOP, but rather as an additional tool in the toolbox.

As usual, video & slides are synchronized. The video is also subtitled. You can find a blog version of the talk (with code samples) below.


Why functional programming? (0:12)

There are different ways of solving problems other than object-oriented programming. You may already know how to solve a problem with OOP, but now Swift makes it very easy and convenient to use functional programming. In fact, some problems may even be easier to solve using functional programming!

Core Image (1:30)

Core Image is a fairly common library used to work with images, adding filters and processing them. In Objective-C, you create a filter and pass in a name as a string parameter. After setting defaults, you set values that must be of type CIImage and NSNumber. However, you may find this API annoying in that you have to constantly type out your value names, so it’s easy to make typos. You might also be checking the documentation to see what values you can set for which key and what the types have to be.

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

The Swift way of doing this improves on these problems. Instead, define a typealias called filter, which is actually a function from CIImage to CIImage. Filter is then used just like a normal thing you would use on an object. Functional programming allows you do pass functions around and use them as parameters.

typealias Filter = CIImage -> CIImage

Creating Filters

Filters (3:51)

The blur filter is a function that takes a blur radius and returns a function from CIImage to CIImage. The braces {} after the return statement indicate that what will be returned is a function, while the keywords “image in” require the function to take an image as an argument. We then set up the parameters, which is just a simple dictionary.

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

To use this filter, we create an NSURL and a CIImage out of the URL. We can then create a blur function with a specific blur radius, and then call that function on the image to filter it.

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

The ColorGenerator function takes in a color and returns a filter. Regardless of the input value, it will return a color.

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

Composite Source Over takes two images, overlays them, and then composites them. It takes in the overlay (top) image and then works the same way. You set up a parameter’s dictionary, create a CIFilter, and then you get the output image. We crop it as well to make sure that it doesn’t get infinite, since Color Generator returns an infinitely sized image.

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

Combining Filters (8:34)

We can then combine these two filters to create one filter that will generate a color and overlay it on top of an image. It first calls colorGenerator to generate a color, then overlays it by calling to compositeSourceOver.

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

We can go even further and combine these filters with the blur filter. We first the blurRadius, generate the color, then blur the image and overlay it with our color.

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

Using Operators (11:08)

The end result has a lot of parentheses - this could get messy very quickly when you chain too many filters together in this way. Instead, we could write another function to combine two filters. This function first applies filter2, then filter1.

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

Using composeFilters, the resulting code from before would now look like this:

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

However, here is where Swift comes in very handy. Swift allows you to define operators, which you can make use of to make this code very readable. However, if you have too many custom operators, this will backfire and instead make your code much harder to understand.

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

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

Defining a Library

Diagrams (14:01)

In Objective-C CoreGraphics, we can draw a simple diagram composed of three shapes. We set colors and then fill shapes in with those colors. It can be hard to make adjustments to code like this. If you wanted to insert a shape between two existing shapes, you have to go back and change the coordinates for every object after the new one.

[[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));

This custom API, written in Swift, is very declarative and allows you to say what you want, rather than how to do it. It simply gives the relative sizes of the shapes and then uses the triple bar operator to place them next to each other.

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

We can then extend this example as we wanted to before, but with much less trouble, by inserting another circle into the diagram.

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

Bar Graphs (17:18)

Bar graphs can be made with the help of the same library using rectangles and text. We can call the barGraph function on a dictionary of keys and values.

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

How does the barGraph function work? We first define the function, set up the values, and then normalize the system. After normalization, every value will be between 0 and 1, and we create a corresponding rectangles. For the labels, we map over the inputs and make the texts that will align with the rectangles through the triple dash operator.

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
}

The Library (19:23)

Now we can finally try to define a library. We have seen three Primitive things, and so we create an enum to represent those. With this, we can define an enum for a Diagram type. It has a primitive case, two diagrams next to or above each other, a diagram with an attribute like a color, and finally one where it is aligned with something. (Recursive enums may not work yet in Swift, but they should!)

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

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

The Draw Function (21:00)

The Draw function is extremely long and has many cases. For parameters, it takes in context, bounds, and a diagram that is switched on. The first cases in the switch draw Ellipses and Rectangles by calculating their frame and then filling them.

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)

The next case deals with text. We set up some attributes and draw the attributed string in a system font of size 12.

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)

The case for drawing Attributes first saves the context, sets the color, and then recursively draws the rest of the diagram. At the end, we restore the context.

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

For the beside and below diagram cases, the code is almost the same. We separate the frame according to size and then draw the diagrams in their separate frames.

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)

The last case is for alignment. The fit function does this for us, so we only need to draw the diagram recursively. The result of this function is that we can put this in an NSView or even generate PDFs.

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

Operators (24:07)

The previous two operators we used, triple bar and triple dash, are custom defined. They are examples of operator overloading, which may not be for everyone, but they were very useful in allowing us to put things next to and below each other. Operators can make it easier to read code when you need to write a lot of nested code.

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

JSON (24:37)

A final topic for the talk involved parsed, untyped JSON dictionaries and turning them into valid, typed dictionaries. This is the JSON we’ll be using as an example:

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

The goal is to parse the above JSON and obtain the following Blog struct:

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

First Attempt (26:51)

The first version of the function parseBlog returns a Blog if the types are correct. The nesting continues for every key and its respective type only if the conditions are met. If all the conditions pass, we can construct the Blog value with the correct types and the values from the optionals.

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
}

The first fix is to make a function that checks if a value is an NSString because we do this twice in the above code. It takes a dictionary, looks at the key and only returns a value if the key is present and is a string. Otherwise, it will return nil.

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

Second Attempt (29:33)

The second version now includes the function written above, string, and looks like this:

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
}

Another small fix does the same as the string function did, but for numbers. The following functions look for a number and casts it if present. We can also define the similar functions for int and bool. For optionals, we can also use map which will only execute if the value exists.

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

Third Attempt (30:32)

Our refactored code now looks even more declarative:

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
}

However, we could further improve this code by dealing with the nested if statements. This flatten function checks if all the optionals are there, and if so, creates one large optional in the form of a tuple.

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
}

Fourth Attempt (32:10)

We look up the four variables, flatten it, and if they are all non-nil, we return the Blog.

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
}

We can continue to work on our code by getting rid of that last if statement. It takes a function from A, B, C, D to R as well as (A, B, C, D), and if both are non-nil, it applies the function to the value.

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
}

Fifth Attempt (33:27)

Now our code looks like this:

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

We can call the function apply on our flattened structure. But to continue refactoring code, we can make apply more generic, as well as do some currying.

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

Currying can be a difficult concept to grasp. It returns a nested function that can be very useful in functional programming. Swift allows you to leave out the parentheses when you nest those functions. By calling apply repeatedly, we can make the code smaller and finally result in our Blog.

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

// 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)

// Has type: Int?
let id = int(blogData, "id")
// 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)

Sixth Attempt (37:37)

Now after simplying the apply function and after currying, this code has many calls to apply.

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

We can define another operator, the star operator. It’s the same function as apply, only using an asterisk.

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
}

Seventh Attempt…Almost There (38:10)

Now the code is almost complete. We’ve replaced the many calls to apply with our custom operator.

// before
return apply(apply(apply(apply(makeBlog, id), name), needsPassword), url) 

// after
return makeBlog <*> id <*> name <*> needsPassword <*> url }

Eighth (and Final!) Attempt (38:42)

All the intermediate statements and if lets have been removed. The types are all correct, so if we accidentally swap two variables, the compiler will complain. Now the final version of our code looks like this:

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

Q&A (40:35)

Q: Your currying was very constrained, is there a way to write a more generic curry?
Chris: Some languages have defaults. To my knowledge, you can’t write a default curry, but you can write them for a certain number of arguments and the compiler will select one for you. Maybe at some point they will add this for Swift.

Q: Is there any support for tail-call optimization?
Chris: I don’t think so, maybe. When I did a lot of recursive things, I would get a lot of crashes. The team likely knows these things and will add it in the future maybe.

Q: In your slides, you used a lot of custom operators. Can you speak a little about how they affect new developers on a team?
Chris: They’re horrible. It really depends on your context - if you come from Objective-C and you’re starting to use Swift, I would not recommend that. The same goes for if you’re working on a larger team. If you have people from a functional language, they these operators may be more familiar.

Q: Coming from an OOP background, the goal is reasonable code. How would you organize your code in functional programming?
Chris: On a higher level, it’s mostly the same. On a bit of a lower level, what I find very useful are a lot of helper functions. You see helper functions a lot that make it more convenient to work with your code. It’s a little like the UNIX philosophy, where you have smaller functions that can be composed into each other. It’s very frustrating in the beginning because you have to restructure your thinking.

Q: In your book, do you also go over collection processing and things like map and reduce?
Chris: Yes, absolutely. When you start with functional programming, you first learn about functions like those. You can do maps on anything, even arrays and optionals and so yes, we definitely go over that.

Q: What’s your opinion on Swift relative to the other programming languages you’ve used?
Chris: So I was originally going to go hiking in Poland, and I was sitting in a mountain hut refreshing Twitter as WWDC was happening. When they introduced Swift, I was amazed and immediately downloaded the eBook. Now we can do all these really cool functional things, but as a language, there are a lot of features that are missing. What I really like about Swift is that we can do functional programming while also accessing all the Cocoa docs. It’s very hard for other languages, like Haskell, to interoperate with Cocoa, and it’s a super powerful combination.

Q: Is there any effort to make an open-source library with these operators and functions, similar to the Scala Z library for Scala?
Chris: Yes, it’s called Swift Z.

Q: I noticed there were no var declarations in your presentation, could you comment on that?
Chris: Functional programming for me is also a lot about immutability, where you create values and you don’t modify them. With the Diagrams, there was no var because there was no thing being mutated. It makes it easier to reason about your code because you know your values aren’t going to be changed. Another advantage is if you do concurrent programming, because it is very difficult to work with mutable objects. There are sometimes disadvantages - it’s hard to write an in-line quicksort in classical languages. If I do use var, I like to isolate it within a function so on the outside, the function looks immutable.

Q: Can you explain your reasoning behind when you set the precedence for the custom operators <*> and not for others?
Chris: I looked at precedence in Haskell operators, and so I thought about what scale would be best to work with. I also looked at precedences for normal Swift operators and that’s how I set it.

Q: Do you think these solutions are scaleable, knowing the learning curves of some organizations?
Chris: It really depends, I would say you should work with the nicest solution possible. It’s not either/or though, you can take some features of functional programming and then slowly build it up if it fits your style.

Q: Have you noticed any changes in memory consumption or other benchmarks when using functional paradigms?
Chris: I would think that if you used mutable data, memory usage would be better. With more lets, you would use more memory and CPU, but you can win a lot in other ways. Your code will be faster and you can optimize in a different way. For example, if you map over an array twice, if this is pure, you can just combine the two maps into one map and then iterate over the array just once. It would be very difficult to write this optimization in other languages like C. The clarity is a huge win, and I’ve never had performance problems.

Q: One of the big benefits of functional programming is laziness, is there a way to do that in Swift?
Chris: Yes, there is a lazy keyword that can make things lazy for you, but I don’t know all the details about it. You can also write generators and sequences, although the documentation on that is low. I’m not too sure how things in Swift work in that regard.


Click to read more

Chris Eidhof

Chris Eidhof

Chris Eidhof is the author of many iOS and OS X applications, including Deckset and Scenery. He has also written extensively on the subject, from his personal blog to objc.io to a variety of books. He formerly ran UIKonf, and still runs frequently.