DEV Community

Khoa Che
Khoa Che

Posted on

Do you think functional code readable ?

Functional programming paradigm aims at correctness and beauty. It produces the most elegant piece of code with a streamlined look and feel.

Personally, I can effortlessly write functional code and would like to write it rather than procedural or object-oriented code. It's more straightforward for me: I'd like to use map rather than for-loop.

But because most of my colleagues just don't get functional programming, they claim my code is unreadable, so unmaintainable. Beginners are scared away from my code.

A sample of my code, making use of lodash a lot:

const get_piece = (board, x, y) =>
  _.chain(board.pieces)
    .filter(piece => piece.current_x == x && piece.current_y == y)
    .first()
    .value();

const process_move = (move, board) => {
  // FIXME the move should be specified by absolute coeff of the piece to move
  const to_move_x = board.hole_x + move[0];
  const to_move_y = board.hole_y + move[1];

  const piece = get_piece(board, to_move_x, to_move_y);

  piece.current_x = board.hole_x;
  piece.current_y = board.hole_y;
  board.hole_x = to_move_x;
  board.hole_y = to_move_y;
};

const adjacent_offsets = Object.freeze([
  [0, 1], // up
  [1, 0], // left
  [0, -1], // down
  [-1, 0] // right
]);

// Check if co-effient is in bound of the board
const in_bound = (board, offset) =>
  (board.hole_x + offset[0] >= 0) &&
  (board.hole_y + offset[1] >= 0) &&
  (board.hole_x + offset[0] < board.split_x) &&
  (board.hole_y + offset[1] < board.split_y);

// Return a random element from an array, uniform distribution
const choice = array => array[Math.floor(Math.random() * array.length)];

const random_move = board => {
  const possible_moves = _.chain(adjacent_offsets)
    .filter(offset => in_bound(board, offset))
    .value();
  const move = choice(possible_moves);

  process_move(move, board);
};

const shuffle = (board, times) => {
  _.range(times).map(() => random_move(board));
};

const is_piece_in_position = piece =>
  piece.current_x == piece.result_x && piece.current_y == piece.result_y;

// return true if all pieces in right position
const check_result = async board =>
  _.chain(board.pieces)
    .map(is_piece_in_position)
    .every()
    .value();

Do you think functional code readable ?

Top comments (5)

Collapse
 
curtisfenner profile image
Curtis Fenner

A few minor things could help:

I don't think using lambdas over regular function definitions gets you much here. If your lambda if split over four lines, the extra return is not a big deal. Using function alone will make the code look much more familiar.

Second, assuming you're targeting modern JavaScript (or have polyfills available), you don't need to use lodash. filter and map are already a standard members of arrays.

Also, you really should document your functions' purposes. I can't really figure out what your shuffle, though it does appear to be mutating board which is not very functional. Where you are mutating objects, you should probably stick to an imperative style.

Also, you should probably not use [0] and [1]; use .x and .y since that's what you mean!

Collapse
 
voanhcuoc profile image
Khoa Che

using lambdas over regular function definitions gets you much here. If your lambda if split over four lines, the extra return is not a big deal. Using function alone will make the code look much more familiar.

Also, you really should document your functions' purposes.

Agree. I wrote this code yesterday, haven't merged yet. At that time I just want to prototype as fast as possible, so less keystroke is a thing :)

you don't need to use lodash

Yes... I just get used to it than native Web API. Also AFAIK, Web API doesn't have range() and few other things.

you should probably not use [0] and [1]; use .x and .y since that's what you mean!

My bad!

Where you are mutating objects, you should probably stick to an imperative style.

How can I write it without this shit var i=0; i<50; i++ ?

Many thanks anyway :)

Collapse
 
curtisfenner profile image
Curtis Fenner

I think

for (let i = 0; i < count; i++) {
    random_move(board);
}

Is probably the clearest way to write it. While for loops aren't very aesthetic, they are simple and very clear, and easily recognizable to JavaScript programmers.

If you really wanted to get rid of it, you could use something like

for (let i of _.range(count)) {

but since this isn't an established idiom, it takes more mental effort to read even if it is prettier.

I notice that random_move is actually doing too much. It shouldn't choose a move and execute it; it should just choose a move. The shuffle function body is the place where you should call process_move.

Thread Thread
 
voanhcuoc profile image
Khoa Che

:)

const loop = _.range;

loop(n).forEach(() => {
  random_move(board)
});

It shouldn't choose a move and execute it; it should just choose a move. The shuffle function body is the place where you should call process_move.

Oh yes. Thank you!

Collapse
 
arobu profile image
Andrei D. Robu • Edited

I am also interested in programming in a functional style, I care quite a lot about it so I want to contribute to the conversation.

Firstly, I want to say that I like your code, but also that the focus should be on how it can be changed to make it more readable for your colleagues.

A point that can be difficult for us to accept is that even though there are some general principles that can lead to programs being more or less readable (coupling, mutation, long functions, etc) readability can still be subjective: when we read a new piece of code, in order to have an easy time understanding the code, we must know what the little pieces do ("while" execute the block repeatedly, "range" returns an array, "if" does conditional execution, etc).

When we develop software with others, we should check first if your colleagues are familiar with or eager to embrace the concepts we want to introduce.

Then, when we program we keep in mind that the target audience is not the compiler (or interpreter), but other people and ask ourselves what we can do to make it easier for them to read the code. Thinking about our audience is not something we do only when programming but, actually, any time we communicate we choose which details to add and which details to leave out to correctly target the audience we have in mind.

To give you my personal experience reading this as an example, I can tell you that I know what the map() function does because I use it often and I even used Object.freeze(), so I know what it does, but I have not seen chain() before and so I don't know what it does. That means that there are parts of the code that I can read and parts of the code that are not easy for me to read and understand. Try to keep in mind that your readers will have some sort of experience like this when reading the code and try to imagine what they will feel and think when you are writing it.

I won't claim to know the solution here. I don't even have much experience working with other programmers myself, but here's a couple of ideas that I think may help.

  • There is a cost to novelty. For example, one reason why we break down large functions into smaller ones is that they would be too complex otherwise. When introducing new functional programming concepts to the code base, maybe you can think of them as being especially costly and that the code around them should be especially clear, to make their usage more obvious (so as an example, maybe don't chain too many unfamiliar functions, but, instead, break down the chain with multiple variable assignments with friendly names).
  • Document the things that may be unfamiliar to your readers. You can add a brief summary to each of your functions saying what the function does, at a high level. Here is an example:
/* Takes the piece from position `move` and moves
it into the hole. Modifies the game board in-place. */
process_move(...) {...}
  • Lastly, a few unit-tests could make a big difference in helping others understand the code. Consider adding a few tests showing what the outputs of functions should be, given some simple example inputs.
// this would be somewhere in the body of a unit test function
assertEqual(get_piece([
  [â™”, â™™],
  [â™™,  ],
], 0, 0), â™”)

Vectors are Cool

There's one last thing I want to add. I don't think it would help with readability in your case, but I want to mention it just because I love vectors. In your code, you have some parts that operate on components of vectors like this.

const to_move_x = board.hole_x + move[0];
const to_move_y = board.hole_y + move[1];

Each of the two lines here does the same operation, but for each component. This could be written more briefly with vectors.

const to_move = Vector.add(board.hole, move)

An example of a vector class implementation can be found here on this gist.