Swift: Curry take-out with a side of Image Processing

800px-Jinbocho_Ladrio_curry_DSCN2711_20111027

For the past day or so, I’ve been trying to wrap my head around currying. Lily B has been incredibly patient, walking me through the process of understanding what currying is, and how it enables you to create specialized functions from a more generic core implementation.

I’ve gotten some feedback that this write-up is too long and convoluted and needs a tl;dr at the top. So here it goes.

Question: Is this currying stuff more trouble than it is worth? Is it a new form of hazing for interviews that you’d otherwise avoid in shipping code?

Answer: Currying has a true killer purpose. It’s great for powerful general functions with multiple tweakable arguments. Use currying to create better entry points. Supply common presets and 3. profit!

Discussion and examples follow.

Currying

In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument (partial application). It was introduced by Moses Schönfinkel and later developed by Haskell Curry. — Wikipedia

Currying in Swift (and other programming language) refers to a technique in which you translate a function that accepts an n-tuple of arguments into a series of functions with single arguments. (Monoples? Singples? I’m going to stick with 1-tuples.)

Swift Currying

In Swift, functions normally look like this:

public func Convolve(var kernel: [Int16], image : UIImage) -> UIImage? {...}

This function, which I’ve elided here for space reasons, uses the Accelerate framework to perform basic image processing. Specifically, it convolves a UIImage instance against an arbitrary kernel made up of weighted Int16 values.

This is a very handy way of creating special effects. This is a perfect example of a function that benefits from currying.

When curried, this function’s declaration looks like this:

public func Convolve(var kernel: [Int16])(_ image : UIImage) -> UIImage? {...}

Notice the one major change? It’s subtle. Instead of a single argument tuple, the declaration consists of a series of one-argument tuples before the return type. There’s an extra set of parentheses in there between [Int16] and image.

Normally, you call the function with two arguments:

Convolve(kernel, image)

With currying, you use two 1-tuples:

Convolve(kernel)(image)

The extra parentheses are a dead give-away. They instantly enable you to spot the curry.

Why Curry

Currying enables you to break up functions into partially-specified components and then use and reuse those components however you like.

And that’s where this language feature becomes deeply powerful. You can decouple the “Convolve(kernel)" part of this call from the “(image)" part. Separating these enables you to delay setting an image argument until some indefinite time in the future. You can now perform assignments like this:

public var blur7Kernel : [Int16] = [Int16](count: 49, repeatedValue:1)
public let Blur7 = Convolve(blur7Kernel)

This snippet creates a re-usable 7×7 blur effect. You can apply it to any image by calling Blur7(image).

Here’s the best part. This isn’t a throwaway usage. You get a new function that can be used over-and-over by many programs in many circumstances: the customization is useful in and of itself but the code that drives the customization is decoupled.

When you update the centralized convolution routine, every curried version updates as well. It’s the same benefit as if you had laboriously written a full wrapper function but with almost no overhead.

func Blur7(image : UIImage)  -> UIImage? {
    var blur7Kernel : [Int16] = [Int16](count: 49, repeatedValue:1)   
    return Convolve(blur7Kernel, image)
}

Building Libraries

In fact, with currying, I can now build an entire suite of common image processing functions by passing various kernels. Each definition raises the level of abstraction and prevents me from having to re-develop kernels (or even think about the math) when I want to emboss, blur, or sharpen an image.

// Embossing
public var embossKernel : [Int16] = [
    -2, -1, 0,
    -1, 1, 1,
    0, 1, 2]
public let Emboss = Convolve(embossKernel)

// Sharpening
public var sharpenKernel : [Int16] = [
    0,  -1, 0,
    -1,  8, -1,
    0,  -1, 0
]
public let Sharpen = Convolve(sharpenKernel)

// Blurring
public var blur3Kernel : [Int16] = [Int16](count: 9, repeatedValue:1)
public let Blur3 = Convolve(blur3Kernel)
public var blur5Kernel : [Int16] = [Int16](count: 25, repeatedValue:1)
public let Blur5 = Convolve(blur5Kernel)
public var blur7Kernel : [Int16] = [Int16](count: 49, repeatedValue:1)
public let Blur7 = Convolve(blur7Kernel)
public var gauss5Kernel : [Int16] = [
    1,  4,  6,  4, 1,
    4, 16, 24, 16, 4,
    6, 24, 36, 24, 6,
    4, 16, 24, 16, 4,
    1,  4,  6,  4, 1
]
public let Gauss5 = Convolve(gauss5Kernel)

Best of all, everything here uses one central function. While each of these filters relies on distinct kernel presets, none of these assignments affects how convolution takes place.

Partial Application

What currying does and why it’s particularly important in Swift and in other languages is that it lets you utilize partial application.

In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.  — Wikipedia

Without built-in currying, you can still create partially-applied functions in Swift. For this example, you could build wrapper closures around the Convolve function by setting kernels like this. It’s not that difficult to put together but why bother?

public func CreateConvolveFunction(kernel : [Int16]) -> UIImage -> UIImage? {
    return {(image : UIImage) -> UIImage? in
        return Convolve(kernel, image)}
}

This kind of intermediate work isn’t needed in Swift. Built-in currying gives you this feature for free. Just add parentheses.

That Plus-Two Thing

If you perform a basic web search, you’ll find any number of write-ups about currying (in Swift and in other languages). Nearly all of these   focus on “oh look, I can create a function that adds 2 to a number so you can map that function across an array”. Here’s what that looks like in Swift.

func addTwoNumbers(x : Int)(y: Int) -> Int {
    return x + y
}
let add2 = addTwoNumbers(2)
[1, 2, 3, 4].map{add2(y:$0)}

The problem with these examples is they pretty much entirely miss the point of currying in Swift. In Swift, you can perform +2 mapping using anonymous arguments with a lot less work:

println([1, 2, 3, 4].map{return $0 + 2})

Unlike Haskell, where you can apply fmap (2+) to an array, Swift cannot curry infix operators. There’s no “2+” advantage.

Currying Costs

Currying comes with a cost, but for most purposes they’re pretty minor. Kevin Roebuck writes, in Functional Programming Languages: High-impact Strategies, “Curried functions may be used in any language that supports closures; however, uncurried functions are generally preferred for efficiency reasons, since the overhead of partial application and closure creation can then be avoided for most function calls.” (Thank you, Google search.)

Reserve currying for highly-parameterized functions. Currying enables you to specialize these to establish re-usable already-primed-and-ready default configurations.

Conclusion

A quick trip back now to tl;dr-land. What is currying? It’s a way to break down a function’s parameters. When you see multiple sets of parentheses one after another, you’re probably dealing with currying.

Currying offers advantages that vary by language. In Swift, what makes it worthy of your attention is its ability to create function variants with built-in presets.

  • Is Currying worth learning? Yes.
  • Will it come up in job interviews? Possibly.
  • Is it really hard to learn? No. Just add in a few parentheses and you’re ready to rock and roll.
  • So why did it take you so long to figure it out? Because all the write-ups with “See? This works with Plus-2!” really didn’t jive with me.

Hopefully my learning through experimentation will help you jumpstart the process.

You can try out the Convolve examples I discuss here by downloading the PAccelerate playground from my github repo.

3 Comments

  • Nice writeup to get up to speed with the concept of currying and where to use it. Thanks for the succinct explanation!

  • Excellent writeup!

  • That picture – ha! Reminds me of the following line from Stargate SG-1 “Threads”, heard in the Astral Diner: “Frank, I need a Noah’s boy in a blanket, two hen fruit wrecked on a shingle with a mystery in the alley, a warm Eve with a moldy lid, and two checkerboards, alright? Oh yeah, hold the pig.”

    Roughly translated: “Ham with pancake; 2 eggs, scrambled, on toast with a side order of hash; warm apple pie with a slice of cheese; and 2 waffles, no bacon.”