Note: I strongly recommend following along in the book with this blog post. I am not going to be recreating the diagrams or the code here, so if you don’t have the book open when you are reading this post, you will be very confused and I would not like that.
This first full chapter of Functional Programming in Swift introduces one of the most important new concepts in Swift: Higher Order Functions. I will get into higher order functions in a moment, but first I would like to give a little bit of context as to what the example of the chapter is about.
Battleship
The example used for this chapter is called Battleship. I, like many other people, associate Battleship with the board game. Since many programming textbooks use common games with known rules to teach programming concepts, it was understandable that many people thought that this example would be about the board game Battleship. This is not the case.
Back in 1994, the Office of Naval Research was exploring rewriting some of its defense software. Like any good agency, they didn’t want to just pick a programming language out of a hat. They wanted to benchmark which language would be the most efficient language to use for this project. They were concerned with a few factors:
– Development Time
– Lines of Code/How Concise is the Code
– Human Readability
– Maintainability
For a very long time, functional programming languages were considered to be impractical for real world applications. They were an interestring curiosity and another approach to programming, but the enforced static nature of functional languages and the lack of mutable variables seemed to doom functional languages to never be implemented in actual projects.
This study proved that a purely functional language could not only be used effectively in a real application, but also proved that this approach could be even better than an imperative approach.
If you look over a lot of the examples in this chapter, you will notice a lot things that are not present in our traditional games of Battleship:
– Checking the range of your weapons
– Avoiding engaging enemies too close to your ships
– Taking the location of friendly entities into your equations
These are not things we think about when playing the board game Battleship, but they are incredibly vital things to consider if you are a naval ship engaged in battle with actual enemy combatants. These are not small considerations. You don’t want to accidentally frag your fighter jets when you are aiming for an enemy ship. Any targeting program that you design must account for all of these considerations.
This beginning chapter takes this example and shows how Hudak and Jones were able to work around these constraints in a functional manner that was more efficient and easier to maintain than the traditional imperative way of approaching these problems.
Imperative Complexity
Our initial calculation is very simple. We are assuming our ship is at a location and we are trying to determine if another point is within range of our ship. Our range is represented by the radius of a circle and we are simply passing the location of our ship (position) and the distance we can shoot (range).
We are then using the Pythagorean Theorem to determine if our target point is within range of our ship. a squared plus b squared equals c squared. In our case, target.x squared plus target.y squared should be less than or equal to our range squared. Our first function is taking the square root of the two target coordinates and returning it only if this qualification is met.
In order to keep things clear, we are using a type alias so that we don’t have to remember that the CGPoint object is our position and the CGFloat is our distance. We will see type alias again, so don’t forget it.
So far, so good.
As the program grows in complexity, it becomes harder to continue to maintain the function. We keep adding additional parameters and constraints. I am going to honestly say at this point I developed code blindness and stopped looking at the code. Suffice it to say, this once simple function grows to a large mess of spaghetti that kind makes me weep for the future of human civilization.
I am not going to untangle the spaghetti code because we aren’t supposed to be doing this anyway and I am not going to encourage you to write unmaintainable code. Bad Swift developer!!
Going to skip ahead to the functional way to deal with these constraints.
The Functional and Rational Approach to This Problem
There is one very important thing to remember about functional programming versus imperative programming:
– Imperative programming is about telling the computer how to do something.
– Functional programming is about telling the computer what to do.
In the above spaghetti code, we were getting over wrapped up in how the conditional logic would deal with all of the various contingencies of our problem. If our point is in range but it is also too close to a friendly, what do we do? It is easy to get overwhelmed by the complexity of implementation.
So let’s take a step back and ask what are we actually trying to do, without thinking about how it will get done.
We want to know if our point is in a region where we can target it.
Remember when I told you to remember type alias? We are bringing it back here. A type alias allows you to assign a label to an existing type so that if you have multiple objects/properties/variables that are all the same type, you can assign a label to the one specific instance of that type.
We know that strings and numbers and classes are all types, but there is another thing in Swift that now qualifies as a type: a function.
Functions in functional programming are values, no different than strings or structs. They can be passed around and referenced exactly the same way you would work with any other kind of value. The best analogy I can think of for this is a string. In C we didn’t have real strings. Stings were an array of chars that were eventually, in later languages, encapsulated into their own type. As you work more with functions as their own discreet values, it should get less weird to think of them in this way.
We can create a type alias for a specific function type. In the example in the book, we are creating a type alias for a function that takes a Position as a parameter and returns a Bool.
So instead of our initial function that uses the Pythagorean theorem and returns the result of that calculation, we are instead returning a closure that performs the calculation. Closures are simply unnamed functions. Basically we are taking the logic we had before and putting a nice, shiny function wrapper around it. We are wrapping a riddle inside of an enigma.
Why would we want to do this?
Let me give you a real world example of this in practice:
I work at a company called SonoPlot. My boss, Brad Larson, wrote a blog post about our project to rewrite our robotics control software in Swift. Since we are dealing with interacting with hardware, there are a lot of things that can go sideways that you don’t have to deal with when you are working with just software. Sometimes something goes screwy when we are communicating with the hardware and if we just send the command again it will go through just fine. Other times, the hardware is disconnected.
We don’t always know which one of these situations we will be in. We don’t want to keep sending commands to our robotics if they are not connected or on. Alternatively, we don’t want to tell the user that the robotics are not connected when they actually are.
To get around this situation, we created a special function called “runCommandAndAttemptSoftRecovery()” that takes a function as a parameter. We wrap many of our hardware functions in this function. “runCommandAndAttemptSoftRecovery()” will try to run the function we are passing in and if it doesn’t pass, it will attempt run the function again. If the function fails a second time, we handle the error.
Sometimes you will have some functionality that is important to apply to many parts of your project, such as our hardware commands. In times like those, being able to pass a function into the function makes it possible to reuse a piece of code in ways that weren’t possible with Objective-C or an imperative language.
In the example in the chapter, we are trying to figure out a way around the creepy spaghetti code and conditional logic. Rather than creating one Frankenstein’s Monster of a function that has to account for all of these things, we are creating a lot of small functions that account for discreet parts of this problem.
We check if the point is within range of the circe. We check the offset. We write a function that combines known areas that are in range so that they are in one data set instead of two. Each of these functions returns a function of the same kind, so they can work together to narrow down more discreetly if a point is within range of the battleship without hitting a friendly craft.
This is a rather difficult example to explain and I will honestly say that at this point I can only explain it on a high level. I strongly recommend looking at the final code and referring back to each individual function to see how they work together. There are truly reusable functions in the final function that behave differently based on their inputs and parameters. Sometimes looking at a final piece of code and following the logic through its various rabbit holes is the best way to understand how something works. That’s why we dissected frogs in high school biology class, even though I swear some of my teachers were sadists….
Takeaways
This is an introductory chapter. It isn’t expected for you to genuinely grok all nuances of functional programming from this.
Here are the points I would take away from this chapter:
- Start thinking about what you want your code to do rather than how you are going to implement it.
- Start trying to mentally break things down into smaller pieces. Instead of having some big, scary function full of state and conditional logic, think about what you are trying to do in smaller steps.
- Get used to the idea that functions can be passed around like variables. Think of functions as things that take one thing and turn it into something else. If you have a piece of wood, that can be turned into many different things. It can be a table, a desk, or a bookshelf. If you think of a function that takes a piece of wood and returns a piece of furniture and think about how each piece of furniture can be different, this might give you a better idea about how to conceptualize passing functions in Swift.
- Don’t get discouraged!! This is a different way of thinking. It isn’t going to be intuitive. I have been working on this for nine months and I have barely gotten to the point where this chapter makes sense. That is one reason I am doing these posts. This is just to get your feet wet with some functional concepts. You are not expected to fully understand everything at this point in time. If you feel dumb right now, join the club. You’re not the only one. Just keep working at it and eventually something will click.
I wanted to recommend some resources I am using to try and work through this book to get these concepts to make more sense:
I will try to add to my list of resources as I encounter new ones. If people have sources they particularly like and want to make me aware of them, please ping me at @redqueencoder
Again, if a lot of this is still super confusing, that’s fine. You’re in the right place. We’re all trying to figure out how to please our new functional overlords. It will take a while and no blog post anyone is going to write is going to make this immediately intuitive. Think back to when you learned programming for the first time. It took a while, didn’t it? Learning how to think takes some time and some work. Don’t get down on yourself. Keep at it.
Up next, we are tackling some Core Image. Stay tuned!!