## DEV Community π©βπ»π¨βπ» is a community of 963,864 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Brandon Weaver

Posted on

# Functional Programming in Ruby β State

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.

This series is a partial rewrite and update of my own series on Medium on Functional Programming. It has been modernized and updated a bit.

## 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, and that's what we need to keep in mind with Ruby: there are always tradeoffs.

### 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.

x = 5
x += 2 # Mutation of state!

That especially applies to methods:

def remove(array, item)
array.reject! { |v| v == item }
end

array = [1,2,3]
remove(array, 1)
# => [2, 3]

array
# => [2, 3]

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. In general it's not great to mutate data that's passed into your function.

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

def remove(array, item)
array.reject { |v| v == item }
end

array = [1,2,3]
remove(array, 1)
# => [2, 3]

array
# => [1, 2, 3]

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:

a + b
end

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.

def count_by(array, &fn)
array.each_with_object(Hash.new(0)) { |v, h|
h[fn.call(v)] += 1
}
end

count_by([1,2,3], &:even?)
# => {false=>2, true=>1}

Note: Newer versions of Ruby have the tally function which would be used like this to get a similar result: [1, 2, 3].map(&:even?).tally

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:

class RubyClub

def initialize
@members = []
end

print "Member name: "
member = gets.chomp
@members << member
end
end

The problem here is that weβre conflating a lot of things in one function:

• Asking a user for a member name
• Getting that name
• Notifying the user we added a member

Thatβs not the concern of our class, it only needs to know how to add a member, anything else is outside the scope of that method.

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?

before do
\$stdin = StringIO.new("Havenwood\n")
end

after do
\$stdin = STDIN
end

ruby_club = RubyClub.new
expect(ruby_club.members).to eq(['Havenwood'])
end
end

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.

class RubyClub

def initialize
@members = []
end

@members << member
end
end

All we need to test now is this:

ruby_club = RubyClub.new
end
end

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 conflated with 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:

SAMPLES_DIR = '/samples/ruby_samples'

def initialize
end

end
end

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:

def initialize(base_path)
@base_path = base_path
end

end
end

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:

SAMPLES_DIR = '/samples/ruby_samples'

def initialize(base_path: SAMPLES_DIR)
@base_path = base_path
end

end
end

### 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:

class RubyClub
def initialize
@members = []
end

@members << member
end

@members << m
end
end
end

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?

class RubyClub

def initialize(members = [])
@members = members
end

@members.concat(members)
end
end

RubyClub.new(new_members)

### 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.

A common point of confusion is that Functional Programming is an entirely new and independent paradigm from Object Oriented Programming, when in fact they share quite a few ideas, and often times are more complimentary than some would like to admit.

## 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:

Understanding Computation

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

Kestrels, Quirky Birds, and Hopeless Egocentricity

As always, enjoy!

K Putra • Edited on

I've tried pure FP in ruby. And it was horrendous. The main problem is ruby GC.

But I still using FP paradigm in some cases. Right now I am working on some ecommerce that use rails as backend. For the calculation that involve money (influencing total payment), I make all the variable immutable by default.

You dont even have to ask about the performance. But imo in this case effectiveness is way more important than efficiency.