It’s functions as far as the eye can see

Andy Bennett
codeburst
Published in
6 min readApr 4, 2018

--

Coming from a “strict” OOP paradigm, when I switched to Swift, one of the more awkward changes to the way I think about code comes from functions being first class types. This means, for the sake of a simple definition, that you can store them in variables and pass them around, for example, into and returning from other functions and class methods. In Objective-C, with block syntax, the idea of passing something that looked like a function into another function became commonplace, but the idea that a function might return another different function, well that’s just crazy talk! It’s precisely this change to the status of functions that’s awkward — primarily because in order to take advantage of the power it provides, you have to think differently about your code.

You’ve may have heard of “imperative” vs “declarative” programming; the discussion about why declarative is awesome is everywhere at the moment. Apologies to any comp-sci people if I’m butchering your terms, but it basically boils down to declarative is what to do; imperative is how to do it. Herein lies the power of functions as first class types; they allow you to encapsulate the how so that you can write code that concentrates on the what.

This is all a bit abstract at the moment, so lets look at a really simple example.

func fade(view: UIView, to value: CGFloat) -> () -> Void
{
return {
view.alpha = value
}
}
let myView = UIView()
let fadeIn = fade(view: myView, to: 1)
myView.alpha = 0
UIView.animate(withDuration: 0.2, animations: fadeIn)

The key takeaway from the fade function is that it’s not actually doing the work when the function is called; it’s encapsulating the information needed to do that work in a new function, which is itself returned.

The fadeIn variable then is a representation of that work, which can be passed around — in this instance used as the closure (or block in Objective-c parlance) in the UIView.animate function. We can do the same for other views and other alpha values.

Or, we could choose not to pass the view when we call the fade function, but have it return a function that needs a view passed in when that is called.

func fade(to value: CGFloat) -> (UIView) -> Void
{
return { view in
view.alpha = value
}
}
let myView = UIView()
let fadeIn = fade(to: 1)
myView.alpha = 0
UIView.animate(withDuration: 0.2, animations: {
fadeIn(myView)
})

In this case fadeIn represents a function that takes any view and sets the alpha on it to 1. Clearly, in this example it would be simpler just to set the alpha directly, but what if we wanted to make the process more complex?

func fadeAndResize(to alpha: CGFloat, transform: CGAffineTransform)
-> (UIView) -> Void
{
return { view in
view.alpha = alpha
view.transform = transform
}
}
let myView = UIView()
myView.alpha = 0
let fadeIn = fadeAndResize(to: 1,
transform: CGAffineTransform(scaleX: 2.0, y: 2.0))
UIView.animate(withDuration: 0.2, animations: {
fadeIn(myView)
})

The eventual call site of the new function (inside the animate closure) has not changed at all, but now we’ve added more functionality, and with relative ease we can animate multiple views.

func fadeAndResize(to alpha: CGFloat, transform: CGAffineTransform)
-> (UIView) -> Void
{
return { view in
view.alpha = alpha
view.transform = transform
}
}
typealias Func = () -> Voidfunc animate(duration: TimeInterval,
animations: @escaping Func) -> (Func?) -> Void
{
return { completion in
let animator = UIViewPropertyAnimator(duration: duration,
curve: .linear,
animations: animations)
animator.addCompletion({ _ in completion?() })
animator.startAnimation()
}
}
let viewOne = UIView()
let viewTwo = UIView()
viewOne.alpha = 0
viewTwo.alpha = 0
let fadeIn = fadeAndResize(to: 1,
transform: CGAffineTransform(scaleX: 2.0, y: 2.0))
let fadeOut = fadeAndResize(to: 0,
transform: CGAffineTransform(scaleX: 0.5, y: 0.5))
let fadeInAnimations = {
fadeIn(viewOne)
fadeIn(viewTwo)
}
let fadeOutAnimations = {
fadeOut(viewOne)
fadeOut(viewTwo)
}
let initial = {
viewOne.alpha = 0
viewTwo.alpha = 0
}
initial()let f1 = animate(duration: 2.0, animations: fadeInAnimations)
let f2 = animate(duration: 2.0, animations: fadeOutAnimations)
f1 { f2(nil) }

I’ve added a new animate function that wraps the UIViewPropertyAnimator which I’m using instead of UIView.animateWithDuration (see this article on why), and I’ve defined a fadeOut and an initial function. Then we’ve created animation block functions (fadeInAnimations, fadeOutAnimations) that combine the fadeIn/Out functions for both the views, and then animate functions using those. Its important to remember that, until the very last line (f1 { f2(nil) }), no animation is taking place. The last line simply runs f1 and then when it completes runs f2.

Right … here we go with the grand finale!

typealias Func = () -> Void
typealias Anim = (UIView) -> Void
func transform(_ transform: CGAffineTransform) -> (UIView) -> Void
{
return { $0.transform = transform }
}
func fade(to alpha: CGFloat) -> (UIView) -> Void
{
return { $0.alpha = alpha }
}
func animate(duration: TimeInterval,
animations: @escaping Func) -> (Func?) -> Void
{
return { completion in
let pAnimator = UIViewPropertyAnimator(duration: duration,
curve: .linear,
animations: animations)
pAnimator.addCompletion({ _ in completion?() })
pAnimator.startAnimation()
}
}
func animator(_ views: [UIView],
_ animations: @escaping Anim,
_ duration: TimeInterval) -> (Func?) -> Void
{
return animate(duration: duration, animations: {
views.forEach { animations($0) }
})
}

We’ve simplified the functions that set the view properties, making each one responsible for a single action, and we’ve added a new animator function that takes a number of views, and applies the animate function to them.

Now we’re going to apply a little bit of operator magic:

func + (anim1: @escaping Anim,
anim2: @escaping Anim) -> Anim
{
return { view in
anim1(view)
anim2(view)
}
}

Firstly, we’re overloading the + operator to allow us to create a new function from 2 animation functions, in effect combining them into one.

precedencegroup ForwardComposition
{
associativity: left
higherThan: AssignmentPrecedence
}
infix operator >>>: ForwardCompositionfunc >>> (a: @escaping (Func?) -> Void,
b: @escaping (Func?) -> Void) -> (Func?) -> Void
{
return { c in
a { b(c) }
}
}

Now we create a new operator that takes two (Func?) -> Void functions and runs the first with the second as a parameter, producing a third. This is quite difficult to follow, but essentially it allows us to write:

let func3 = func1 >>> func2
func3(nil)
// or(func1 >>> func2)(nil)// instead of func1 { func2(nil) }// as we did before

Now assuming we’ve got a couple of views (viewOne, viewTwo) as child views of a third (parentView) we could put all of this together like this:-

// define the transform functions
let transformIn = transform(CGAffineTransform(scaleX: 2.0, y: 2.0))
let transformOut = transform(CGAffineTransform(scaleX: 0.5, y: 0.5))
// define the fade functions
let fadeIn = fade(to: 1)
let fadeOut = fade(to: 0)
// combine the functions
let fadeInAnimations = fadeIn + transformIn
let fadeOutAnimations = fadeOut + transformOut
// set the initial state
let initial = {
viewOne.alpha = 0
viewTwo.alpha = 0
}
initial()
parentView.setNeedsDisplay()
// define the animations
let f1 = animator([viewOne, viewTwo], fadeInAnimations, 0.5)
let f2 = animator([viewOne, viewTwo], fadeOutAnimations, 0.5)
// compose the animations
let f3 = f1 >>> f2 >>> f1 >>> f2 >>> f1 >>> f2
// nothing has actually run yet!// now run it all
f3(nil)

We get an animation of the views zooming and fading in, then out, 3 times; but each of the pieces is broken down into very small components, so we could create any number of variations on how this works, and combine them in all sorts of different ways.

It also demonstrates how we can use functions that return new functions (transform, fade, +, >>>, animator, etc) to define the how of all the work that needs to be done, allowing the rest of the code (transformIn/Out, fadeIn/Out, fadeInAnimations, fadeOutAnimations, f1, f2, f3) to remain concerned only with the what.

Hopefully it’s easy to see how we could use this to create some really complex animations, whilst maintaining code reusability and readability.

Gist of complete code for Xcode 9.3 playground

✉️ Subscribe to CodeBurst’s once-weekly Email Blast, 🐦 Follow CodeBurst on Twitter, view 🗺️ The 2018 Web Developer Roadmap, and 🕸️ Learn Full Stack Web Development.

--

--