Skip to content

Exploring Functional Programming Without Java 8

While everybody is excited about Java 8, many of us did not have the chance to try it out at work. Not yet. How “functional” can our code be if we are stuck with Java 7? Is Functional Programming without Java 8 possible in Java and at what cost?

A short story

At work we have a tech exchange group and organize many events – lectures and even a coderetreat. When we organized our second coderetreat, some of the people that participated the first time did not come. Reason: the problem was the same, so they thought it is not going to be fun anymore. They wanted something that is not Game Of Life this time. While I do not completely agree, they had a point.

How to keep things fun? Simple – we organized a Coding Dojo. Goal of Dojo is the same as coderetreat – environment where people can practice, learn and teach. Each Dojo session we worked on a different problem. I capped the The participant count to 8 and we used one computer and a projector to code. So everybody will write something small, then pass to the next person. Repeat until we consider problem as finished. The first exercise was to implement part of the so called “Greed” dice game (aka Yahtzee).

Greed dice game rules

1. You roll 5 dice.
2. Each one you roll is worth 100 points.
3. Each five you roll is worth 50 points.
4. Three ones are worth 1000 points instead of 300.
5. Three twos score 200, triple threes – 300, same with fours (400), fives (500) and sixes (600).

There could be many additional rules.

First Solution

We wrote a lot of tests and arrived at something similar to this:

public int score(int... dices) {
    int score = 0;
    int[] counts = new int[7];
    for (int dice : dices) {
        counts[dice]++;
        if (dice == 1) {
            score += 100;
        } else if (dice == 5) {
            score += 50;
        }
        if (counts[dice] >= 3) {
            if (dice == 1) {
                score += 1000 - 300;
            } else if (dice == 5) {
                score += 500 - 150;
            } else {
                score += dice * 100;
            }
            counts[dice] -= 3;
        }
    }
    return score;
}

This is just our score method. Tests are not here, but we had tests for every possible scenario. Code is working. For me it was interesting to see how other people solved this. Most of them just “automated” what they would do when they calculate the scores in their minds. This can be seen in code above. While working, it has issues.

  • Everything happens in one for loop – we count dices and we score them at the same time.
  • There are too many ifs.
  • Weird numbers. Why add 1000 and subtract 300 when there are three ones? First version actually had add 700 instead.
  • It is not clear what the code wants to do.
  • First version also used a Map instead of an array to store counts.

Solution refactored

public int score(int... dices) {
    int score = 0;
    int[] counts = new int[7];
    for (int dice : dices) {
        counts[dice]++;
    }
    //score triples
    for (int dice = 1; dice < 7; dice++) {
        if (counts[dice] >= 3) {
            if (dice == 1) {
                score += 1000;
            } else {
                score += dice * 100;
            }
            counts[dice] -= 3;
        }
    }
    score += counts[1] * 100;
    score += counts[5] * 50;
    return score;
}

This looks better. We can extract all score triple logic in a separate method as well if we want to.

Separate parts of code now have only one responsibility and that is good. We are not counting and scoring at the same time, no weird numbers as well.

But why do we have to refactor that much to get where we want to be? Can’t we speed this up somehow? Is there any shortcut?

Declare What, not How

Let’s look at the game rules again.

Greed score needs to include score for ones, score for fives and score for triples.

So lets start writing code that we want to see from beginning.
Something like:

scoreThreeOfAKind();
scoreOnes();
scoreFives();
return score;

We can even write it like this:

public int score(int... values) {
    int[] counts = counts(values);
    Score score = new Score(counts);
    return score.
            scoreThreeOfAKind().
            scoreOnes().
            scoreFives().
            value();
}

Code declares just WHAT needs to happen. There is no HOW.
Score is an immutable object. Now we need to implement it and see the cost we need to pay for our nicely looking code here.

public class Score {
    private final int[] counts;
    private int value = 0;
    public Score(int[] counts) {
        this.counts = counts;
    }
    public Score scoreThreeOfAKind() {
        if (counts[1] >= 3) {
            value += 1000;
        }
        for (int i = 2; i<=6 ; i++) {
            if (counts[i] > 2) {
                value += i * 100;
            }
        }
        return this;
    }
    public Score scoreOnes() {
        value += 100 * numbersToScore(counts[1]);
        return this;
    }
    public Score scoreFives() {
        value += 50 * numbersToScore(counts[5]);
        return this;
    }
    private int numbersToScore(int count) {
        if (count >=3) {
            count -= 3;
        }
        return count;
    }
    public int value() {
        return value;
    }
}

Lets explain this:

  • We tell the Score object what to do, don’t ask.
  • Each method – scoreOne, scoreFives, scoreTriples will take one input and produce one output. Like a math function.
  • All relevant code is in the method.
  • There is no global state, i.e not that error prone.
  • Each function is independent and have no side effects. We can change the order of calling and they will still work.

What is the link with Functional Programming?

Declarative Style, work in Independent Functions with No Side Effects and Immutability are all Functional Programing paradigms.

Conclusion

All this is just an exercise and is not pure “functional”. I will probably explore this a bit more and try to implement it in Java 8 or Scala. There are also many more rules that are missing from the original “Yahtzee” game.

Remember we can just refactor our first solution, enforce Single Responsibility Principle and arrive to a similar outcome. But why refactor when we can start with the code we want to read later?

Thank you for reading.

Tags:

Leave a Reply

Your email address will not be published.