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.
Related
I have an object describing a menu with submenus at different levels. Before transforming it into HTML, I add an id property that will later be used to generate ids for HTML. Here's the code:
MyModule.recursively_add_id = function myself(arg) {
let idGenerator = () => { return '_' + (Math.random()*0x1000000000000000) ; }
if (arg === null)
return ;
else if (Array.isArray(arg))
arg.forEach(element => myself(element)) ;
else if (typeof arg === 'object') {
for (let key in arg)
myself(arg[key]) ;
arg.id = idGenerator() ;
}
else // number, string...
return ;
} ;
I never defined an inner function in a function designed to be called recursively, so I wondered, will idGenerator() redefined at each call of recursively_add_id()? I guess it wouldn't be a great overhead, but I'd like to be sure of what is going on here.
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');
...
};
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));
In Javascript I've written code for a reducer to handle the adding & removing of strings from an Array. This code works fine:
const taskId: string = action.payload;
let taskIds: ?Array<string> = state.selectedTaskIds;
if (!taskIds) {
taskIds = [taskId];
} else if (taskIds.includes(taskId)) {
if (taskIds.length === 1) {
taskIds = null;
} else {
const idx = taskIds.indexOf(taskId);
taskIds = [...taskIds.slice(0, idx), ...taskIds.slice(idx +1, taskIds.length)];
// DNW: taskIds = taskIds.filter((selTaskId: ?string) => selTaskId !== taskId);
}
} else {
taskIds.push(taskId);
}
What doesn't work is the commented out line prefixed with "DNW" that is supposed to accomplish what the 2 lines of code above it do. The Flow error message I get is:
Missing type annotation for T. T is a type parameter declared in
array type [1] and was implicitly instantiated at call of method
filter [2].Flow(InferError)
I've tried several things but couldn't resolve it, which is why I used the other approach. But I am most curious what this error message is telling me and how to resolve it.
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.