5
\$\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
(source: teropa.info)

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?
Glorfindel
1,1133 gold badges14 silver badges27 bronze badges
asked Dec 10, 2016 at 1:33
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

You can use Destructuring assignment in importing functions from ramdajs.

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

answered Feb 2, 2019 at 21:51
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.