6
\$\begingroup\$

I'm just learning functional programming in JavaScript using Ramda.

I took this tutorial and implemented the server core.js component using Ramda.

The idea is that we'll have a collection of things to vote from: Movies, songs, programming languages, Horse JS quotes, anything. The app will put them against each other in pairs, so that on each round people can vote for their favorite of the pair. When there's just one thing left, that's the winner.

I attempted to use the point-free style as much as possible as I found this really forced me to avoid writing imperative code. I also avoided making any changes to the data structure defined by the tutorial though I felt that doing so could have potentially simplified some things quite a bit. In real projects we can't always change an API.

Here is the data structure used:

voting data structure

Here's my code:

const R = require('ramda');

// overCalculatedValue :: Lens s a -> ( s -> (a -> a)) -> ( s -> s)
const overCalculatedValue = R.curry( (lens, func) => {
  return R.converge( R.over(lens), [func, R.identity]);
});
// setCalculatedValue :: Lens s a -> ( s -> a) -> ( s -> s)
const setCalculatedValue = R.curry( (lens, func) => {
  return R.converge( R.set(lens), [func, R.identity]);
});

// createEntryTallyLens :: string -> Lens s a
const createEntryTallyLens = R.useWith( R.lensPath, [R.flip(R.append)(['vote', 'tally'])] );
// isVotingOnLastCandidates :: State -> boolean
const isVotingOnLastCandidates = R.compose( R.equals(0), R.length, R.prop('entries'));
// pickWinner :: [[string],number] -> [ string, number] -> [[string],number]
const pickWinner = R.curry( ( result, candidate ) => {
  return result[1] === candidate[1] ? [ R.append(candidate[0], result[0] ), result[1]] :
            result[1] < candidate[1] ? [ [candidate[0]], candidate[1] ] : result;
});
const NoWinner = [[],-Infinity];

// getWinnerList :: State -> [string]
const getWinnerList = R.compose( R.nth(0), R.reduce(pickWinner, NoWinner), R.toPairs(), R.path(['vote', 'tally']));
// isWinnerDetermined :: State -> boolean
const isWinnerDetermined = R.compose( R.equals(1), R.length, getWinnerList);
// getOverallWinner :: State -> string
const getOverallWinner = R.compose(R.nth(0), getWinnerList);
// getNextCandidates :: State -> [string]
const getNextCandidates = R.compose(R.take(2), R.prop('entries'));
// addWinnerToList :: State -> ([string]->[string])
const addWinnerToList = R.compose(R.flip(R.concat()), getWinnerList);

// createNewVote :: State -> State
const createNewVote = R.compose( R.over(R.lensProp('entries'), R.drop(2)), setCalculatedValue(R.lensPath(['vote', 'pair']), getNextCandidates), R.omit('vote'));
// addWinnerBackToEntries :: State -> State
const addWinnerBackToEntries = overCalculatedValue( R.lensProp('entries'), addWinnerToList);

// doesWinnerExist :: State -> boolean
const doesWinnerExist = R.allPass([isVotingOnLastCandidates, isWinnerDetermined]);
// declareWinner :: State -> State
const declareWinner = R.compose( R.omit(['entries', 'vote']), setCalculatedValue( R.lensProp('winner'), getOverallWinner));
// setupNextElection :: State -> State
const setupNextElection = R.compose( createNewVote, addWinnerBackToEntries);

// setEntries :: State -> [string] -> State
exports.setEntries = (state, entries) => {
  return R.set(R.lensProp('entries'), entries, state);
};

// vote :: State -> string -> State
exports.vote = ( state, entry ) => {
  return R.over( createEntryTallyLens(entry), R.compose(R.inc, R.defaultTo(0)), state);
};

// startNextVote :: State -> State
exports.startNextVote = R.ifElse(doesWinnerExist, declareWinner, setupNextElection);

My questions:

  • Is converge with set or over the correct way to update a value in an object that must be calculated from other values in the object?
  • Any other areas where this could be simplified or improved?
\$\endgroup\$
0
\$\begingroup\$

You can use Destructuring assignment in importing functions from ramdajs.

const {compose, reduce, ....} = require('ramda'); instead of using R.compose.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.