We continue our example of CoreImage CIFilters, this time showing how function composition can facilitate working with filters a bit more flexible and chainable. Starting with the imperative, method-based approach, then gradually building towards a pattern that allows us to easily build filters functionally, swapping out the order, and changing around input parameters.
Episode Links Source Code Functional Swift Presentation by Chris Eidhof - I saw Chris give a version of this talk in Milan, and I enjoyed it, particularly the example about CIFilters. It was the inspiration fort his episode. Thanks Chris! Functional Programming in Swift - Speaking of Chris Eidhof, he has written an excellent book on functional programming Swift with Florian Kugler and Wouter Swierstra. As someone fairly naïve with functional programming concepts, this book has helped relate these concepts to Swift. Highly recommended. Defining our Function Type Creating a name for our functions can do a lot to help with clarity. typealias Filter = CIImage -> CIImage Using this we can build functions that use this as a parameter without dealing with -> explosion everywhere, which can be hard to read. In addition, giving something a name means you can reason about it more abstractly. Defining our Filter Functions func sepia() -> Filter { return { image in let filter = CIFilter(name: 'CISepiaTone', withInputParameters: [ kCIInputImageKey: image ]) return filter.outputImage } } func blur(radius: Double) -> Filter { return { image in let filter = CIFilter(name: 'CIGaussianBlur', withInputParameters: [ kCIInputImageKey: image, kCIInputRadiusKey: radius ]) return filter.outputImage } } Using these is a little awkward at first: let output = blur(4.0)(sepia()(cimg)) Here it's hard to change the order of these, reason about the parameters, etc. We can introduction function composition to make this a bit easier to read. Function Composition func composeFilter(f1: Filter, f2: Filter) -> Filter { return { image in f2(f1(image)) } } With this in place, we can compose our 2 filters into a new filter: let blurSepia = composeFilter(sepia(), blur(4.0)) let output = blurSepia(cimg) This is a lot nicer to work with, because it reads in the order of the filters. It gets harder when you want to compose another filter with it: let blurSepia = composeFilter(tint(UIColor.redColor), composeFilter(sepia(), blur(4.0)) let output = blurSepia(cimg) It felt nice for 2 filters, but 3 got ugly. Imagine a 10 filter step... ouch. If you'll permit me, we can even go a tad further with a custom operator. (Insert standard warning of caution around inventing operators here). For no apparent reason, I chose the • symbol. This character can be typed on a Mac by pressing ⌥ 8. infix operator • { associativity left } func •(f1: Filter, f2: Filter) -> Filter { return { image in f2(f1(image)) } } Now any number of filters can be composed easily: let output = tint(UIColor.redColor) • sepia() • blur(4.0)