How to efficiently validate all args in Javascript functions? - javascript

I work with Javascript (Typescript), more specifically with React. So this question is written in Javascript but it's more like a general question.
I'm trying to refactor some code and extract static methods. Once I extract the method, I find myself checking if all arguments received are valid. This becomes hard to read and, sometimes, the real purpose of the function has only a few lines.
Here's an example of how I would end up writing a method / function
const isGreaterThan = (value1, value2) => {
if(typeof value1 !== 'number'){
console.error("Invalid argument. value1 must be a number");
return 0;
}
if(typeof value2 !== 'number'){
console.error("Invalid argument. value2 must be a number");
return 0;
}
return value1 > value2
}
Now imagine if this method receives an Array
const isGreaterThanAll = (values, value1) => {
if(!values instanceof Array){
console.error("Invalid argument. values must be an Array");
return 0;
}
if(!values.every(value => typeof value === 'number')){
console.error("Invalid argument. elements of values must be numbers");
return 0;
}
if(typeof value1 !== 'number'){
console.error("Invalid argument. value2 should be type of number");
return 0;
}
return values.every(value => value1 > value);
}
Now imagine passing an Object or an Array of Objects. Depending on the complexity of the arguments, this becomes harder to follow.
Some of my questions are:
Which is the best practice to refactor code in this scenario?
How can I refactor this kind of code to be more readable?
Should I build a method to check arguments?

One way to do it is to create a recursive function like this.
const isNumberOrArrayOfNumbers = (x) => {
if (typeof x === 'number') {
return true;
}
if ( Array.isArray(x) ) {
return x.every(n => isNumberOrArrayOfNumbers(n));
}
return false;
};
const isGreaterThanAll = (values, value1) => {
let valid = [values, value1].every(a => isNumberOrArrayOfNumbers(a));
if (!valid) {
console.error('Invalid');
return Number.NaN;
}
return values.every(value => value1 > value);
};
console.log( isGreaterThanAll([1,2,3,5,6,7],12) );
console.log( isGreaterThanAll([1,2,3,5,6,7],4) );
console.log( isGreaterThanAll([1,2,3,"r",5,6,7],12) );
If you're willing to change the signature of the function you could also refactor by making it variadic and using rest operators
That might look something like this:
const isGreaterThanAll = (n, ...m) => {
let valid = [n, ...m].every(x => typeof x === 'number');
...
};

Related

Pure functions in javascript

I understand functions need to be pure in order to avoid side-effects. The the following function for example:
//Approach 1
buildValidationErrors(state){
let validationErrors = [];
if(state.name === null)
{
validationErrors.push("Name");
}
if(state.email === null)
{
validationErrors.push("Email");
}
if(state.mobile === null)
{
validationErrors.push("mobile");
}
return validationErrors;
}
//Approach 2
_buildError(state,itemName,validationErrors){
if(state[itemName] === null){
validationErrors.push(itemName);
}
}
buildValidationErrors1(state){
let validationErrors = [];
_buildError(state,"Name",validationErrors );
_buildError(state,"Email",validationErrors);
_buildError(state,"mobile",validationErrors);
return validationErrors;
}
In "Approach 1", you have a long function that builds an array. In "Approach 2" I am extracting the reusable logic to "_buildError" to avoid duplication of logic.
However, in Approach 2, the parameter validationErrors is passed in and it is updated as well causing the function to become 'impure' to my understanding.
From that perspective, can the function be pure and compact?
You can avoid passing the errors array by merging the results outside the _buildError() function:
_buildError(state,itemName){
return state[itemName] === null ? itemName : null;
}
buildValidationErrors1(state){
let validationErrors = [];
validationErrors.push(
_buildError(state,"Name"),
_buildError(state,"Email"),
_buildError(state,"mobile")
).filter((a)=> a !== null);
return validationErrors;
}
However, that does not really change the purity of the function. In your 2nd example, the function depends and change only its parameters, thus it is "pure" enough for automated tests and other practical purposes.
I would consider
_buildError(state,itemName){
return state[itemName] === null ? itemName : null;
}
// reduce if you need more than one thing in the validation array
let validationErrors = ["Name","Email","mobile"]
.reduce((acc,item) => {
if (_buildError(state,item)) acc.push({item,something:state[item].something}); return acc },
[] );
// filter if you just need the item name
let validationErrors = ["Name","Email","mobile"]
.filter(item => _buildError(state,item));

Could you have multiple ways to run a function in Javascript?

I am setting up a database package in Node.js and would like to not have separate functions for writing to the database like this:
write(key, val) and write({key: val, key2: val2}). I've seen other solutions on Stack Overflow and other websites and would like to have the simplest solution so my function would "know" whether it was a key, val pair or a JSON object. For example:
if (argtype == "kvp") { // key val pair
databaseJSON[key] = val;
flushToDB(databaseJSON);
} else {
let j = databaseJSON;
for (let i in Object.values(obj)) j[Object.keys(obj)[i]] = Object.values(obj)[i];
flushToDB(databaseJSON);
}
Thank you!
If two arguments are passed, just assign the val to the key property of the database, otherwise Object.assign the one argument to the database, to put all of its properties and values from the passed object to the database:
function write(key, val) {
if (val !== undefined) {
database[key] = val;
} else {
Object.assign(database, key);
}
flushToDB(database)
}
const database = {};
const flushToDB = db => console.log('flushing');
function write(key, val) {
if (val !== undefined) {
database[key] = val;
} else {
Object.assign(database, key);
}
flushToDB(database)
}
write('key', 'val');
console.log('db:', database);
write({ key2: 'val2', key3: 'val3' });
console.log('db:', database);
Because the database here is a plain object, not JSON (something in JSON format is a string, which is not the case here), better to call it database rather than databaseJSON. (See There's no such thing as a "JSON Object")
You can use this.
function foo() {
if (arguments.length == 1) {
// use your object code on arguments[0]
let j = databaseJSON;
let obj = arguments[0]
for (let i in Object.values(obj))
j[Object.keys(obj)[i]] = Object.values(obj)[i];
flushToDB(databaseJSON);
} else {
//user arguments[0] and arguments[1] for you key value
databaseJSON[arguments[0]] = arguments[1];
flushToDB(databaseJSON);
}
}
You can also throw error if arguments.length == 0

Maybe monad sample code in javascript explanation

I'm starting or trying to learn functional programming monads.
So the first is Maybe. I'm trying to convert the code with maybe monad.
function(fieldName, vals, fields) {
var newValue = vals[fieldName];
if (typeof(newValue) == 'undefined') {
var elemFrom = fields[fieldName];
if (elemFrom) {
newValue = fields[fieldName]
}
}
if (typeof (newValue) != 'undefined') {
return newValue
}
}
Here I have a bunch of checks for undefined which i think is good use of monay.
My problem is that I read that you pass value to the maybe monad and map function.
However in my case I replace the value inside the monad.
If I pass null the map method will do nothig since the value is undefined.
I'm not using a framework, i want simple implementation so I can understand it.
Should I add "else" method in the maybe monad class (function).
I have the opposite case "Do something if the value is undefined"
Can you suggest how to solve the issue
Thank you
So the function you posted could be rewritten as
const f = (a, b, c) => b[a] === undefined ? c[a] : b[a];
It isn't clear to me that this needs to be a function at all rather than being inlined wherever you want to use the relevant object properties, but maybe you're partially applying it or something, I'm not judging.
As for Maybe, a (very simple) implementation might look something like this:
class Maybe {
static of (value) {
return new Maybe(value);
}
// Proper solution here should be recursive to handle
// nesting properly, but I'm lazy
static equals (a, b) {
return a.chain(x => x) === b.chain(x => x);
}
constructor(value) {
this._value = value;
}
map (f) {
// Does not distinguish null from undefined, but YMMV. Note
// that if the Maybe value is null or undefined we never touch
// f, that's the null propagation thing.
return this._value == null ? this : new Maybe(f(this._value));
}
chain (f) {
const result = this._value == null ? this : f(this._value);
console.assert(result instanceof Maybe);
return result;
}
}
Now we can test that it obeys the Monad laws:
const a = 3;
const f = x => Maybe.of(x * x);
Maybe.of(a).chain(f) === f(a) // left identity
Maybe.equals(Maybe.of(5).chain(Maybe.of), Maybe.of(5)); // right identity
And that it's a valid Functor
Maybe.equals(Maybe.of(3).map(x => x), Maybe.of(3)); // identity
Maybe.equals( // composition
Maybe.of(3).map(x => x + 2).map(x => x * 3),
Maybe.of(3).map(compose(x => x * 3, x => x + 2))
);
Sweet.
So now, to your function. It would be rewritten as
const f = (a, b, c) => {
return b[a] === undefined ? Maybe.of(c[a]) : Maybe.of(b[a]);
}
Perhaps you see now the reason for my confusion, Maybe isn't really saving you much here. But if I were using Maybe I'd rewrite the whole thing like this:
const or = (a, b) => {
return Maybe.of(a == null ? b : a);
}
And then I would just pass in the property accesses:
const obj1 = { a: 2, c: 3 };
const obj2 = { b: 4 };
const prop = "a"
const result = or(obj1["prop"], obj2["prop"]); // Maybe(2)
Update
Credit to #Bergi in the comments reminding me about Alternative. You could add a method to the Maybe class above like so:
alt (x) {
if (!(x instanceof Maybe)) {
throw new TypeError("Expected a Maybe");
}
return this.chain(x => x) == null ? x : this;
}
// semantics
Maybe.of(null).alt(Maybe.of(3)); // Maybe(3)
Maybe.of(2).alt(Maybe.of(4)); // Maybe(2)
// usage
Maybe.of(obj1[prop]).alt(Maybe.of(obj2[prop]));
Note that this doesn't totally satisfy as an implementation of Alternative (you'd also need a zero/empty method) but you can read here and here for more details. This is probably the best replacement for the function you posted.

Resolving Flow type errors

I am writing an extension to Array, and trying to incorporate typechecking with Flow, but have an error that I can't seem to work out.
The function Sum below has an optional argument of transformer, and if transformer is undefined, gives it a default. It then loops through the array and, if the element is a number, applies the transformer function and adds the result to sum.
Flow is giving the warning that "Function cannot be called on possible undefined value". I thought that the checks that transformer is defined and is a function should mitigate this? What is the best solution?
// #flow
export function Sum(transformer : ?(a : number) => number) : number {
const transformerIsUndefined = typeof(transformer) === "undefined";
if (!transformerIsUndefined && typeof(transformer) !== "function") {
throw "Transform argument must be a function";
}
if (transformerIsUndefined) {
transformer = item => item;
}
let i : number = 0;
let sum : number = 0;
while (i < this.length) {
if (typeof(this[i]) === "number") {
sum += transformer(this[i]);
} else {
throw "Non-numerical element in the array.";
}
i++;
}
return sum;
}
I resolved this by changing the argument to required, and providing a default in the function signature:
export function Sum(transformer : (a : number) => number = item => item) : number {
...
}
I would still be interested in other takes on this.

Return array of functions as an array of booleans

So I'm working on updating some old projects and I am trying to find a source or an example of something I'm trying to accomplish.
what I have
// sample object of functions
var functions = {
required : function(value){
return value.length > 0;
},
isObject : function(value){
return typeof value == 'object';
}
};
Above is a sample of functions in an object. What I want to know is can the following be done:
pseudo code
//user input
var funcs = [required(''), isObject({key : 'v'})];
// what the function I'm building will process, in a sense
functions[funcs[i]](//arguments from funcs[i]);
// what would remain after each function
funcs = [false, true] // with an end result of false
I'm not 100% sure that this can't be done, I'm just not sure how in the slightest something like this would be able to come about. Let's bounce some ideas around here and see what we come up with.
Let me know if you all need any clarification of anything I asked. Thank you ahead of time for all help!
clarification on what I am trying to achieve
The object of functions is not finite, there can be any amount of functions for this specific program I am writing. They are going to be predefined, so user input is not going to be an issue. I need to be able to determine what function is called when it is passed, and make sure any arguments passed with said function are present and passed as well. So when I pass required(''), I need to be able to go through my object of functions and find functions['required'] and passed the empty string value with it. So like this functions['required']('').
other issues
The functions object is private access and the user won't have direct access to it.
How about this.
var functions = {
required : function(value){
return value.length > 0;
},
isObject : function(value){
return typeof value == 'object';
}
};
// Because these values are user inputs, they should be strings,
// so I enclosed them in quotes.
var funcs = ["required('')", "isObject({key: 'v'})"];
funcs.map(function(e) {
return eval('functions.' + e);
});
Running this should gives you an array of return values from the functions in the object.
Trivially, this could be done with:
var tests = [functions.required(''), functions.isObject({key: 'v'})];
If that's all you need, consider that my answer.
For a more general approach, the right tool here seems to be Arrays.prototype.map(). However, since you have an object containing all your functions instead of an array of functions, you'll need some way to make the correspondence. You can easily do this with a separate array of property names (e.g., ['required', 'isObject']). Then you could do something like this:
var functions = {
required : function(value){
return value.length > 0;
},
isObject : function(value){
return typeof value == 'object';
}
};
var args = ['', {key: 'v'}];
var results = ['required', 'isObject'].map(
function(test, i) {
return functions[test](args[i]);
}
);
Of course, if functions were an array instead of an object, you could simplify this:
var functions = [
/* required : */ function(value){
return value.length > 0;
},
/* isObject : */ function(value){
return typeof value == 'object';
}
];
var args = ['', {key: 'v'}];
var results = functions.map(
function(test, i) {
return test(args[i]);
}
);
If you wanted to encapsulate this a bit, you could pass the args array as a second argument to map(), in which case inside the function you would use this[i] instead of args[i].
Sure it's possible. Something like this:
var results = [];
var value = "value_to_pass_in";
for(var f in functions)
{
results.push(f.call(this, value));
}
UPDATE:
function superFunc(value)
{
var results = [];
for(var f in functions)
{
results.push(f.call(this, value));
}
return results;
}
superFunc("value_to_pass_in");
What you want is a map function. You can mimic it like this (I guess if you want one line):
https://jsfiddle.net/khoorooslary/88gh2yeh/
var inOneLine = (function() {
var resp = {};
var i = 0;
var fns = {
required : function(value){
return value.length > 0;
},
isObject : function(value){
return typeof value == 'object';
}
};
for (var k in fns) resp[k] = fns[k](arguments[i++]);
return resp;
}).apply(null, [ '' , {key : 'v'}]);
console.log(inOneLine);
var functions = {
required : function(value){
return value.length > 0;
},
isObject : function(value){
return typeof value == 'object';
}
};
var funcs = ["required('')", "isObject({key: 'v'})"];
function f(funcs){
return funcs.map(function(e) {
return eval('functions.' + e);
});
}
console.log(f(funcs));

Categories