0
\$\begingroup\$

I'm trying to understand the Functional Programming Paradigm (or style) implemented with Javascript. There are a few things I have difficulties with. The naming conventions, and dependency injections. I would be super happy if you could help me out with these two problems.

const items = ['🐶', '🐹', '🦊', '🐼'];

const pickRandomArrayNumber = (floorFunction, randomFunction) => array => floorFunction(randomFunction() * array.length);
const pickRandomItemFrom = pickRandomArrayNumber(Math.floor, Math.random);
console.log(items[pickRandomItemFrom(items)]);

This piece of code grabs a random number between 0 and the length of the provided array. However, I wanted the getRandomArrayNumber function to be independent.

I'm not happy with the names of the functions as they are right now. Mainly because getRandomArrayNumber doesn't really get an array number, but instead provides a function that does.

Is this Functional Programming style future proof and does it follow the Functional Programming Principles correctly? And how would you name these functions? Or perhaps, handle dependency injection in a functional style?

Thanks in advance!

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Nitpicks about naming:

pickRandomArrayNumber is a function that returns a function (and that in turn does something I actually need); it does not itself pick an "array number" (I don't actually know "array number" is either; a number from an array? if you meant index then call it index)

pickRandomItemFrom is not picking an item so don't call it that; it's picking an index of this array; I think "From" is implicit and does not need to be written; if you omit it there would be no other way to interpret what the function does. For whatever reason I stuck with "element" (as in array element) instead of "item" (as in list item) but I think that's more of a personal preference.

If you have a function called pickRandomItem then that it should be made/constructed/returned by something I would call makeRandomItemPicker; it actually does what it says in the name.

Other observations:

I don't think I ever needed anything else than Math.floor in this case. If it's a random elements from an array (all uniform) and the rng happens to return [0..1) floats then the only implementation I can think of is exactly the one you wrote float2uint(rng() * array.length). Notice that I just called it float2uint because that's the only thing it needs to do - convert a float to a uint; I also wouldn't want to assemble functions as a API user - I just want to use it, not think about which float2uint is best for me etc etc. Providing the rng is however useful as sometimes I want to use a custom seedable one and the default JS one isn't seedable. I'd set Math.random as the default for the rng function for convenience.

I also would not want to assemble these functions myself and use this API at all. I would like to call a simple pickRandomElement(array); in items[pickRandomItemFrom(items)] I have to type the array twice for no reason; what if I make a mistake and type a similarly named array of different size instead? I would (possibly very rarely) either go out of bounds and get undefined or not sample the full size array and never find out about it.

All in all I'd rewrite it like so:

const makeRandomItemPicker = (randomFunction = Math.random) => array => array[Math.floor(randomFunction() * array.length)];
const pickRandomItem = makeRandomItemPicker(); 

// alternatively if I have my seedable rng instance pass that
// const pickRandomItem = makeRandomItemPicker(myRng); 
 
const items = ['🐶', '🐹', '🦊', '🐼'];
console.log(pickRandomItem(items));
\$\endgroup\$
1
  • \$\begingroup\$ Indeed Adrianton, I meant "index" instead of "number". Also, great argument about the Item actually being an Element from an Array. I don't exactly like float2uint as it's not descriptive of what actually happens (a pseudo random uint based on the array length). I learned a lot from the mindset of the API user not assembling the functions themselves. It should indeed just be API calls. Thank you! \$\endgroup\$ Apr 15, 2021 at 11:27
3
\$\begingroup\$

Naming

The name pickRandomItemFrom is too long

  • pick implies removal which the function does not do
  • from is redundant.

Thus pickRandomItemFrom can simply be randomItem with Item implying an array

Ignoring the obvious problems and removing the unnecessary currying we can rewrite your code as

const randomItem =  array => array[Math.floor(Math.random() * array.length]);
console.log(randomItem(items));

Math.random is not pure

The function Math.random is not pure because you can not control the seed value. Using it in any function will make that function impure.

To be pure a function must return the same value given the same input.

You can create a pure random function but the function must take a seed value from which to calculate a random value.

Example

const seededRandom = (() => {
    const max = 2576436549074795, add = 8507698654323524, scale = 8765432352450986;
    return seed => ((scale * Math.abs(Math.floor(seed))) + add) % max;
})();

Selecting random item

You can then create the randomItem function using

const randomItem = ((rand) => (arr, seed) => arr[rand(seed) % arr.length])(seededRandom);

Used as follows

console.log(randomItem([1,2,3], Date.now()));

Note that the function can be called many times every ms. To ensure a unique seed modify the seed each call.

Example

const seededRandom = (() => {
    const max = 2576436549074795, add = 8507698654323524, scale = 8765432352450986;
    return seed => ((scale * Math.abs(Math.floor(seed))) + add) % max;
})();

const randomItem = (rand => (arr, seed) => arr[rand(seed) % arr.length])(seededRandom);

const test = [1, 2, 3, 4, 5, 6, 7, 8];

var seed = Date.now();
console.log(randomItem(test, seed++));
console.log(randomItem(test, seed++));
console.log(randomItem(test, seed++));
console.log(randomItem(test, seed++));

// or
console.log("-------------------------");
console.log(randomItem(test, seed = seededRandom(seed)));
console.log(randomItem(test, seed = seededRandom(seed)));

// or curried
const randomizeItems = ((random) => (arr, seed) => () => arr[seed = random(seed) % arr.length])(seededRandom);

const randomTestItem = randomizeItems(test, seed);

console.log("-------------------------");
console.log(randomTestItem())
console.log(randomTestItem())
console.log(randomTestItem())
console.log(randomTestItem())

Note Random means pseudo random in context of this answer

\$\endgroup\$
1
  • \$\begingroup\$ Hey Blindman! Thank you for taking time out of your day to answer my question. I appreciate it a lot. I love how you've rewritten the code so that dependency injection is still possible but without a make function by using an IIFE. You're right about the Math.random() not being pure. I completely forgot about how everything should be pure in functional programming for testing. Your arguments about the naming are spot on! Thank you. \$\endgroup\$ Apr 15, 2021 at 11:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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