Functional Programming in Ruby — State

Brandon Weaver
May 13, 2018 · 8 min read
Image for post
Image for post
FP Lemur Wizard

Ruby is, by nature, an Object Oriented language. It also takes a lot of hints from Functional languages like Lisp.

Contrary to popular opinion, Functional Programming is not an opposite pole on the spectrum. It’s another way of thinking about the same problems that can be very beneficial to Ruby programmers.

Truth be told, you’re probably already using a lot of Functional concepts. You don’t have to get all the way to Haskell, Scala, or other languages to get the benefits either.

The purpose of this series is to cover Functional Programming in a more pragmatic light as it pertains to Ruby programmers. That means that some concepts will not be rigorous proofs or truly pure ivory FP, and that’s fine.

We’ll focus on examples you can use to make your programs better today.

With that, let’s take a look at our first subject: State.

Functional Programming and State

One of the prime concepts of Functional Programming is immutable state. In Ruby it may not be entirely practical to forego it altogether, but the concept is still exceptionally valuable to us.

By foregoing state, we make our applications easier to reason about and test. The secret is that you don’t entirely need to forego it to get some of these benefits.

Defining State

So what is state exactly? State is the data that flows through your program, and the concept of immutable state means that once it’s set it’s set. No changing it.

That especially applies to methods:

By performing that action, we’ve mutated the array we passed in. Now imagine we have two or three more functions which also mutate the array and we get into a bit of an issue.

A pure function is one that does not mutate its inputs.

It’s slower, but it’s much easier to predict that this is going to return us a new array. Every time I give it input A, it gives me back result B.

Has That Ever Really Happened?

Problem is, one can preach all day on the merits of pure functions, but until you find yourself in a situation where it bites you the benefits may not be readily apparent.

There was one time in Javascript where I’d used reverse to test the output of a game board. It would look fine, but when I added one more reverse to it all of my tests broke!

What gives?

Well, as it turned out the reverse function was mutating my board.

It took me longer than I want to admit here how long it took me to realize this was happening, but mutation can have subtle cascading effects on your program unless you keep it under control.

That’s the secret though, you don’t have to exclusively avoid it, you just need to manage it in such a way that it’s very clear when and where mutations happen.

In Ruby, frequently state mutations are indicated with ! as a suffix. Not always, though, because methods like concat break those rules so keep an eye out!.

Isolate State

One method of dealing with state is to keep it in the box. A pure function might look something like this:

When given the same inputs, it will always give us back the same outputs. That’s handy, but there are ways to hide tricks from it.

Strictly speaking, we’re mutating that hash for each and every value in the array. Not so strictly speaking, when given the same input we get back the exact same output.

Does that make it functionally pure? No. What we’ve done here is created isolated state that’s only present inside our function. Nothing on the outside knows about what we’re doing to the hash inside the function, and in Ruby this is an acceptable compromise.

The problem is though, isolate state still requires that functions do one and only one thing.

Single Responsibility and IO State

Functions should do one and only one thing.

I’ve seen this type of pattern very commonly in newer programmers code:

The problem here is that we’re conflating the idea of adding a member with giving a message back to the user. That’s not the concern of our class, it only needs to know how to add a member.

At first this seems harmless, as you’re only really getting input and outputting at the end. The problems we run into are that gets is going to pause the test, waiting for input, and puts is going to return nil afterwards.

How would we test such a thing?

That’s a lot of code. We have to intercept STDIN (standard input) to make it work which makes our test code a lot harder to read as well.

Take a look at a more focused implementation, the only concern it has is that it gets a new member as input and returns all the members as output.

All we need to test now is this:

It’s abstracted from the concern of dealing with IO (puts, gets), another form of state.

Now let’s say that your Ruby Club has to also run with a CLI, or maybe load results from a file. How do you refactor it to work? Your current class is entrenched in the idea that it has to get input and deal with output.

This adds up to very brittle tests and code that are going to give you problems over time.

Static State

Another common pattern is to abstract data into constants. This alone isn’t a bad idea, but can result in your classes and methods being effectively hardcoded to work in one way.

Consider the following:

It’s great as long as you’re only concerned with that specific directory, but what if we need to make a sample loader for elixir_samples or rust_samples? We have a problem. Our constant has become a piece of static state we cannot change.

The solution is to use an idea called injection. We inject the prerequisite knowledge into the class instead of hardcoding the value in a constant:

Now our sample loader really doesn’t care where it gets samples from, as long as that file exists somewhere on the disk. Granted there are potential risks with caching as well, but that’s an exercise left to the reader.

A way to cheat this is by using default values, set to a constant, but for some this may be a bit to implicit. Use wisely:

IO State — Reading Files

Let’s say your Ruby Club has an idea of loading members. We remembered to not statically code paths this time:

The problem this round is that we’re relying on the fact that the members file is not only a file, but also in a JSON format. It makes our loader very inflexible.

We’ve become entangled in another type of IO state: we’re too concerned with how we load data into our club.

Say you wanted to switch it out with a database like SQLite, or maybe even just use YAML instead. That’s a very hard task with the code like it is.

Some solutions to this problem I see from newer developers are to make multiple “loaders” to deal with different types of inputs. What if it’s none of the concern of our club in the first place?

If we extract the entire concept of loading members, we could have code like this instead:

Wait, isn’t this just Separation of Concerns?

The fun thing about OO and FP is that a lot of the same concepts can apply, they just tend to have different names. They may not be exact overlaps, but a lot of what you learn from a Functional language may feel very familiar from best practices in a more Imperative style language.

In a lot of ways, keeping state under control is an exercise in separation of concerns. Pure functions coupled with this can make exceptionally flexible and robust code that is easier to test, reason about, and extend.

Wrapping Up

State in Ruby may not be entirely pure, but by keeping it under control your programs will be substantially easier to work with later. In programming, that’s everything.

You’ll be reading and upgrading code far more than you’re outright writing it, so the more you do to write it flexibly from the start the easier it will be to read and work with later on.

As I mentioned earlier, this course will be more focused on pragmatic usages of Functional Programming as they relate to Ruby. We could focus on an entire derived Lambda Calculus scheme and make a truly pure program, but it would be slow and incredibly tedious.

That said, it’s also fun to play with on occasion just to see how it works. If that’s of interest this is a great book on the subject:

If you want to keep exploring that rabbit hole, Raganwald does a lot to delight here:

As always, enjoy!

Next up are Closures:

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store