I've faced a problem which I can't understand. So I created a new function that contains a couple of functions. This is my code:
(() => {
function BetterArray(array) {
this.array = array;
}
BetterArray.prototype.map = function (fn) {
return Array.prototype.map.call(this.array, fn);
};
BetterArray.prototype.collect = function (fn) {
return Array.prototype.map.call(this.array, fn);
};
const a = new BetterArray([1])
.map((item) => item * 2)
.collect((item) => item * 2);
console.log(a);
})();
In line "collect" I got an error Uncaught TypeError: (intermediate value).map(...).collect is not a function. I'm just curious why this error is appearing and how can I write this code properly to avoid that. Additional thing is that when I change map with collect - I do not receive this error.
I know that both functions are the same, but I want to be sure that it's not based on the function body.
Thank you for your time!
Right now, the .map and .collect methods are returning plain arrays, and plain arrays don't have a .collect method on them.
When you call .map on a BetterArray, you should create and return a new instance of a BetterArray if you want to be able to call BetterArray methods like .collect on it afterwards:
(() => {
function BetterArray(array) {
this.array = array;
}
BetterArray.prototype.map = function (fn) {
const arr = Array.prototype.map.call(this.array, fn);
const newBetterArr = new BetterArray(arr);
return newBetterArr;
};
BetterArray.prototype.collect = function (fn) {
return Array.prototype.map.call(this.array, fn);
};
const a = new BetterArray([1])
.map((item) => item * 2)
.collect((item) => item * 2);
console.log(a);
})();
(if you want .collect to result in a BetterArray instance as well, apply the same logic for the .collect method)
Related
In the following code I am trying to add decorators to a function. For certain reasons I would like to display the function attribute "name". However, I have no access to it as soon as I am in the individual functions. Also, I'm not sure why the functions are called from the bottom up. What are the reasons for all of the points mentioned and how can I avoid them?
let rectangleArea = (length, width) => {
return length * width;
}
const countParams = (fn) => {
return (...params) => {
console.log('countParams', fn.name)
if (params.length !== fn.length) {
throw new Error(`Incorrect number of parameters for ${fn.name}!`);
}
return fn(...params);
}
}
const requireIntegers = (fn) => {
return (...params) => {
console.log('requireIntegers', fn.name)
params.forEach(param => {
if (!Number.isInteger(param)) {
throw new TypeError(`Params must be integers at ${fn.name}!`); //Can't access fn.name
}
});
return fn(...params);
}
}
//Why running from bottom to top?
rectangleArea = countParams(rectangleArea);
rectangleArea = requireIntegers(rectangleArea);
console.log(rectangleArea(20, 30, "hey"));
The first time you make a decorated function for a given function, that returned function does not have a name -- it is anonymous. So when you then pass that decorated function to be decorated again, fn will be that anonymous function.
To solve this, assign the name of the fn function also to the returned decorated function. That way the name will stick even when you decorate that function again, and again...
Here is a helper function that will assign the name property to a given function:
const setName = (deco, value) => {
Object.defineProperty(deco, "name", {value, writable: false});
return deco;
}
let rectangleArea = (length, width) => {
return length * width;
}
const countParams = (fn) => {
return setName((...params) => {
console.log('countParams', fn.name)
if (params.length !== fn.length) {
throw new Error(`Incorrect number of parameters for ${fn.name}!`);
}
return fn(...params);
}, fn.name);
}
const requireIntegers = (fn) => {
return setName((...params) => {
console.log('requireIntegers', fn.name)
params.forEach(param => {
if (!Number.isInteger(param)) {
throw new TypeError(`Params must be integers at ${fn.name}!`); //Can't access fn.name
}
});
return fn(...params);
}, fn.name);
}
rectangleArea = countParams(rectangleArea);
rectangleArea = requireIntegers(rectangleArea);
console.log(rectangleArea(20, 30, "hey"));
Why the functions are called from the bottom up.
Because in your decorator the last step is to call fn.
That fn might be an already decorated function, and so it is normal that earlier decorations of the function run later.
It is like wrapping a birthday present several times, each time with a different color of wrapping paper. When your friend unpacks it, they will get to see the colors of wrapping paper in the reverse order from the order in which you had applied them.
So you want to do something additional with your decorators? They need some common behavior? We've shown we know how to do that already: with decorators. Your decorators need decorators of their own!
Here I write a decorator-decorator keep which takes a decorator function and returns a new decorator function which keeps the name and length properties of the function passed to it. (Say that five times fast!)
It uses the same technique as the answer from trincot, but is less intrusive, as you can simply wrap the decorator functions just as you would the underlying ones. Here I do that at definition time, since we never really want these decorators without this behavior, but you can do it as you like.
let rectangleArea = (length, width) => {
return length * width;
}
const keep = (decorator) => (fn) =>
Object .defineProperties (decorator (fn), {
name: {value: fn .name, writable: false},
length: {value: fn .length, writable: false}
})
const countParams = keep ((fn) => {
return (...params) => {
console.log('countParams', fn.name)
if (params.length !== fn.length) {
throw new Error(`Incorrect number of parameters for ${fn.name}!`);
}
return fn(...params);
}
})
const requireIntegers = keep ((fn) => {
return (...params) => {
console.log('requireIntegers', fn.name)
params.forEach(param => {
if (!Number.isInteger(param)) {
throw new TypeError(`Params must be integers at ${fn.name}!`); //Can't access fn.name
}
});
return fn(...params);
}
})
//Why running from bottom to top? -- answered by #balastrong
rectangleArea = countParams(rectangleArea);
rectangleArea = requireIntegers(rectangleArea);
console.log(rectangleArea(20, 30));
console.log(rectangleArea(20, 30, "hey"));
.as-console-wrapper {max-height: 100% !important; top: 0}
The name keep was originally keepName before I realized that I personally would also want the function to keep the arity intact. I couldn't come up with a clear useful name for this... which is a large warning sign to me. So there may be something still wrong with this design.
It's not from bottom to top, it's the order you set.
With your decorators, you basically just did this:
requireIntegers(countParams(rectangleArea(20, 30, "hey"))).
Which means first it executes requireIntegers by passing it as input everything else (countParams(rectangleArea(20, 30, "hey"))).
Then you see the console log and the error as params.forEach scans the params and finds 'hey' which is not a number.
I am trying to loop through an argument that is passed to a method and I am getting a TypeError: individualExpenses.map is not a function. What am I doing wrong here?
class ExpenseTracker {
constructor(payCheck, monthlyExpenses) {
this.payCheck = payCheck;
this.monthlyExpenses = monthlyExpenses;
}
storeExpenses(individualExpenses) {
let expenseStore = [];
individualExpenses.map(expense => {
expenseStore.push(expense)
})
console.log(expenseStore)
}
}
const v = new ExpenseTracker({}, {});
v.storeExpenses(1)
You are passing a numerical value to storeExpenses function and applying map over it. map works only on arrays. If you do
v.storeExpenses([1]);
it'll work just fine.
Alternatively, you can build logic to convert a non-array type to an array and use it in your storeExpenses function. This way you can do either of v.storeExpenses(1) or v.storeExpenses([1]) and the function will still work.
e.g.
const wrapToArray = (obj) => {
if (!obj) return [];
return Array.isArray(obj) ? obj : [obj];
};
and then modify your storeExpenses method as below -
storeExpenses(individualExpenses) {
let expenseStore = [];
wrapToArray(individualExpenses).map(expense => {
expenseStore.push(expense)
})
console.log(expenseStore)
}
The code below works for me
Promise.all([first, second, third]).then([first, second, third] => {
console.log(second);
});
I know that console.log(second) will give me the value with the key second.
My promises are dynamically set and now it looks like below:
let collection = [second, third];
Promise.all(collection).then((collection) => {
console.log(collection);
});
In this example I set two values in collection. In real life it can include more or less values.
When I use console.log(collection) it will output collection[0] and collection[1]. In this case I don't know what which value collection[1] is.
Question
How can I, like my first example, have something like named dynamically arguments like collection['second'] or similar?
As we want to access the value dynamically, set collection to an empty object first. Then, use the keys from collection to pass all its Promise-values to Promise.all. Then, map back the fulfilled values and then, we can access collection's value by some key.
let collection = {}
for (let i = 0; i < 3; i++) {
collection[`key${i}`] = Promise.resolve(i)
}
let collectionKeys = Object.keys(collection)
Promise.all(collectionKeys.map(key => collection[key]))
.then(values => {
let collectionFulfilled = collectionKeys.reduce((obj, key, i) => {
obj[key] = values[i]
return obj
}, {})
console.log(collectionFulfilled)
})
If you pass your promises embedded inside an object with a single key, you could use that for it's name, and then with a simple helper function reverse the values & keys from this.
With the new ES6 you can then just pass like -> [{one}, {two}, {three}] etc.
Below is an example with a helper function called namedPromiseAll.
function namedPromiseAll(named) {
const pcollection =
named.map(m => Object.values(m)[0]);
const ncollection =
named.map(m => Object.keys(m)[0]);
return Promise.all(pcollection).then((c) => {
return c.reduce((a,v,ix) => {
a[ncollection[ix]] = v;
return a;
}, {});
});
}
const second = Promise.resolve(2);
const third = Promise.resolve(3);
const collection = [{second}, {third}];
namedPromiseAll(collection).then(console.log);
Following code add map method to function prototype so we are able to map our function, which basically composing map function with the result of mappable function. I understand that
Function.prototype.map = function (f) {
const g = this
return function () {
return f(g.apply(this, arguments))
}
}
But did not understand the following
const Box = x => ({
map: f => Box(f(x)),
fold: f => f(x),
inspect: () => `Box(${x})`
})
const nextCharForNumberString = str =>
Box(str)
.map(s => s.trim())
.map(s => new Number(s))
.map(s => s + 1)
.map(s => String.fromCharCode(s))
.fold(s => s.toLowerCase())
console.log(nextCharForNumberString(' 64 '));
Could you help me to understand that. Box is a function Box(x) and I am losing track after that.Why those parenthesis ( ({ })) and how that thing is working.
Thanks for your time and understanding,
By the way first code is taken from
https://medium.com/#dtinth/what-is-a-functor-dcf510b098b6
Second code is coming from egghead.io's functional javascript first lesson (it is a shame I just stuck in the first lesson)
It might help to inflate the code out of es6 syntax.
function Box(param) {
return {
map: function(fn) { return Box(fn(param)); },
fold: function(fn) { return fn(param) },
inspect: function() { return `Box(${param})`;
}
}
an example: let a = Box(1); returns the object from the Box function and gives you access to a.map, a.fold, or a.inspect
a.map takes a function as a parameter. let's say that function returned the value passed in plus 1
function myFunction(n) { return n + 1 };
you could call a.fold(myFunction) and the returned value would equal 2. This is because our initial param was 1, and our function takes that parameter as its argument and returns.
Basically fold applies whatever function to your param and returns it.
map is similar except that it returns Box(myFunction(1)) which returns a new Box object with a param + 1 since that's what our function mutated our param to.
inspect simply outputs a string that tells you what the current value of param is.
Overview:
function add1(n) { return n + 1 }
let a = Box(1); //param is 1.
a.fold(add1); //translates to add1(1);
a.map(add1); //translates to Box(add(1)), aka, Box(2)
a.inspect(); //translates to "Box(2)"
I'm using a JavaScript spy library,
simple-spy.
I've found out that when spying on a given function,
the resulting spy always has an arity of 0.
This creates a problem with my use of
this currying function.
So I've submitted a pull-request
that adds arity transparency to
the spy library.
The code looks like this:
function spy(fn) {
const inner = (...args) => {
stub.callCount++;
stub.args.push(args);
return fn(...args);
};
// ends up a string like
// 'a,b,c,d'
// depending on the `fn.length`
const stubArgs = Array(fn.length)
.fill(null)
.map((m, i) => String.fromCodePoint(97 + i))
.join();
const stubBody = 'return inner(...arguments);';
// this seems to be the only way
// to create a function with
// programmatically specified arity
const stub = eval(
// the wrapping parens is to
// prevent it from evaluating as
// a function declaration
`(function (${stubArgs}) { ${stubBody} })`
);
stub.reset = () => {
stub.callCount = 0;
stub.args = [];
};
stub.reset();
return stub;
}
exports.spy = spy;
This seems to work.
Is it possible to do this
without the use of eval?
Is it possible to
reduce the use of eval
to even less that this?
I'm aware that there are other issues
with this spy implementation.
It is simplistic and it works
for my use case so far.
Like Benjamin wrote, I used a simple:
function spy(fn) {
const stub = (...args) => {
stub.callCount++;
stub.args.push(args);
return fn(...args);
};
stub.reset = () => {
stub.callCount = 0;
stub.args = [];
};
stub.reset();
Object.defineProperty(stub, 'length', {value: fn.length});
return stub;
}
exports.spy = spy;
Much, much better looking.