# Functional Programming in Ruby

July 21, 2019

[ruby] [functional-programming] [object-oriented-design]

A functional programming riff on a common coding test.

During my most recent round of interviewing, I was given the following problem:

Iteratively design, test and build a simple game of BlackjackBlackjack is the American variant of a globally popular banking game known as Twenty-One. .

I was given the following stub of a test spec to start:

# deck_spec.rb
RSpec.describe Deck do
subject(:deck) { Deck.new }

it "creates a new deck of cards" do
expect(deck).to be_a(Deck)
expect(deck.count).to eq(52)
end

it "can be shuffled"

it "can draw a card"
end



Now, before we go on about the value of such coding tests, or whether this one is a particularly good or bad one, let’s just use this as a personal exercise: How can we solve this problem with code such that it doesn’t run afoul of one of the thornier problems in systems design: Keeping track of all the things in a system such that every change in the system is consistent. Avdi Grim made an excellent Ruby Tapas screencast about this problem and used Contextual Identity as part of the solution.

Let’s try a different approach, one that uses functional programming techniques insteadIf you’re using ReactJS, this will look quite similar to the state property. . Functional programming, at its simplest, enforces everything in the system to be immutable and that to make changes, one applies a function that returns a new and complete state of the system.

Lather, rinse, repeat.

So, let’s try to make a Blackjack system that operates in FP-ish way. Unlike functional programming languages that guarantee immutability, I’m going to use the ice_nine gem to freeze all of my Ruby objects.The Ruby Object class has a #freeze method, which prevents an element from further modifications but it doesn’t work for elements that are inside another element (nested elements).

# deck.rb
require "ice_nine"
require "ice_nine/core_ext/object"

class Deck

def initialize(cards = self.class.default_cards)
@cards = cards
deep_freeze
end

def count
cards.count
end
end


This immediately diverts us into the question, “What is a Card object?” While it is certainly not a Value Object, it can be sorted and compared. When you want to get fancy, they have Suits as well. I’m going to spare you the development of these classes, because it distracts from the core of this article, but you can get copies of them from my blackjack-ruby repository. So, just shade your eyes a little, here’s how we get started, our initializer can create a default deck of cards.

# deck.rb
CARDS = [["A", "Ace", 1]] + (2..10).map { |x| [x.to_s, x.to_s, x] } + [["J", "Jack", 11]] + [["Q", "Queen", 12]] + [["K", "King", 13]]
CARDS.deep_freeze
# https://en.wikipedia.org/wiki/High_card_by_suit
SPADES = Suit.new("♤", "Spades", 4); CLUBS = Suit.new("♧", "Clubs", 1); HEARTS = Suit.new("♡", "Hearts", 3); DIAMONDS = Suit.new("♢︎", "Diamonds", 2)

class Deck
...
def self.default_cards
# https://www.rubytapas.com/2016/12/08/episode-459-array-product/
[SPADES, CLUBS, HEARTS, DIAMONDS].product(CARDS).map { |(suit, card)| Card.new(suit, *card) }
end
...
end


With the preliminaries out of the way, and our first test is now green, let’s go on to the next one:

# deck_spec.rb
RSpec.describe Deck do
...
it "can be shuffled" do
new_deck = deck.shuffle

expect(new_deck).not_to be(deck)
expect(new_deck.cards).not_to eq(deck.cards)
end
...
end


The Ruby Array class implements a #shuffle method and in a normal class, we’d do something like this:

# deck.rb
def shuffle
@cards = cards.shuffle
end


But that will fail because the Deck is frozen (I’m totally digging the informative error message).

FrozenError: can't modify frozen Deck


Remember, however, that our initializer accepts a collection of Cards…

# deck.rb
def shuffle
self.class.new(cards.shuffle)
end


So the #shuffle method shuffles the current Deck, but instead of modifying it, it returns a brand new Deck using the shuffled cards in the initializer.

Next, what about #draw? We need to keep the Card that was drawn from the Deck, and we need a deck without the card in it. This ain’t Dogs Playing Poker, sweet cheeks.

# deck_spec.rb
it "can draw a card from the top of the deck" do
card, new_deck = deck.draw

expect(card).to eq(deck.top)
expect(new_deck.count).to eq(deck.count - 1)
end


The return signature is an Array that we nicely destructure out into the two variables, card and new_deck. Because all we need to do is initialize a new deck with the desired cards, we can do this:

# deck.rb
def draw
[cards.first, self.class.new(cards[1..-1])]
end


Believe it or not, we’ve completed the implementation of Deck! It has a very small surface area, but it does all the things that we want a deck of cards to do (for this card game, anyway). Let’s move on to making a card Game class.

Object Oriented Design is all about managing state through messages. Our Game (of Blackjack) class can deal out hands to get things started, and it can draw a card for a single player. Let’s start with the latter, first, because dealing out hands is very much like repeatedly drawing cards for each player in turn.

RSpec.describe Game do
it "can deal out hands to each of the players"
it "can draw a card for a player" do
card = game.draw(game.players.first)

expect(game.deck.count).to eq(51)
expect(game.players.first.hand.count).to eq(1)
expect(card).to be_a(Card)
end
end


This test will fail because we haven’t defined what our “local” game is. Let’s keep this game simple, shall we? Our game of Blackjack will use 1 deck of cards and have 2 players: “Player A” and the “Dealer”.You’ll never see a game like this in Las Vegas. Ever.

# game_spec.rb
RSpec.describe Game do
subject(:game) {
Game.new(
Deck.new.shuffle,
[ Player.new("Player A"), Player.new("Dealer") ]
)
}


Whoops, our tests are still failing because I wrote the code that I wished I had, including the Player class that I haven’t written yet. Players are yet another immutable class and they have only 2 attributes: The player’s name and the hand of cards.

# player.rb
class Player

def initialize(name, hand = [])
@name = name
@hand = hand
deep_freeze
end
end



Okay, back to our test… The simple start to the problem is just tell the Deck to draw a Card from the deck

# game.rb
def draw(player)
card, new_deck = deck.draw

@players = players.map {
|p| p == player ? Player.new(p.name, p.hand + [card]) : p
}
@deck = new_deck
end


Whoops, there’s that FrozenError: can't modify frozen Game exception that keeps us from cheating. So, how do we solve this? Does #draw create a brand new Game each time? That seems semantically odd. We’ve missed something in our modeling. What are we really doing when we draw a card? We’re changing the state of the Game, so let’s model each “turn” as well, a Turn.

# turn.rb
class Turn

def initialize(deck, players, description = "")
@deck = deck
@players = players
@description = description
deep_freeze
end
end


A Game becomes an ordered collection of Turns that are deterministically transformed from one turn to the next by one function, #draw. This is also our only point of mutability.

# game.rb
class Game

def initialize(deck, players)
@history = [Turn.new(deck, players, "Initial game set up")]
end

# Some convenience accessors
# to look at the "current" state of the Game
def latest_turn
history.first
end

def deck
latest_turn.deck
end

def players
latest_turn.players
end
...
end


and now rewrite #draw to push new Turns onto our game history.

# game.rb
# Draws a card from the deck
# Adds a new turn on the game history (most recent first)
# Returns the card that was drawn
def draw(player)
card, new_deck = deck.draw
next_turn = Turn.new(new_deck, update_player_hand(card, player), "#{card} => #{player}")
history.unshift(next_turn)

card
end

private

# Returns an updated player hand, very ReactJSish
def update_player_hand(card, player)
players.map { |p| p == player ? Player.new(p.name, p.hand + [card]) : p }
end


Dealing out the initial hands is super easy, since #draw does all of the heavy lifting.

# game.rb
# Deal N cards to each player
def deal(cards)
cards.times do
players.each do |player|
draw(player)
end
end
end


We’ve got a pretty good set up now. Let’s take the code for a spin.

game = Game.new(Deck.new.shuffle, [Player.new("Player A"), Player.new("Dealer")])
=> #<Game:...>
game.deal(2)
=> 2
puts game.history.map(&:description).reverse
Initial game set up
2♢︎ => Player A
2♤ => Dealer
Q♢︎ => Player A
9♤ => Dealer


Pretty neat, huh?

We can go in all sorts of directions, now. We still haven’t built any rules for Blackjack, yet, but now that we have a Game class to manage the state of the card table, we’ve got a strong foundation.

Functional Programming in Ruby - July 21, 2019 - Ken Mayer