Can't access function attribute inside decoration function - javascript

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.

Related

Intermediate value in new created prototype

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)

Defining chained string transform functions in Javascript

Chaining Methods, also known as Cascading, refers to repeatedly calling one method after another on an object, in one continuous line of code.
Writing code like this:
str.replace("k", "R").toUpperCase().substr(0,4);
is not just pleasurable and convenient but also succinct and intelligible. It allows us to read code like a sentence, flowing gracefully across the page. It also frees us from the monotonous, blocky structures we usually construct.
This blog talks about how to do it generally, however considering extending the String prototype will be good enough for my case, I don't know whether it is an overkill or not.
Basically I don't know enough Javascript to make the judgement call of,
whether go with the above general way,
or just extend the String prototype, knowing that it is generally a bad practice
or maybe using function wrapper (or maybe it is totally irrelevant)
I also know the following is another option, but it's kind of rigid, I.e., translated into my case, it means the string transform can only be
replace("k", "R") then followed by toUpperCase() then followed by substr(0,4).
// https://medium.com/javascript-scene/javascript-factory-functions-with-es6-4d224591a8b1
console.log('Begin');
const withConstructor = constructor => o => {
const proto = Object.assign({},
Object.getPrototypeOf(o),
{ constructor }
);
return Object.assign(Object.create(proto), o);
};
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
// or `import pipe from 'lodash/fp/flow';`
// Set up some functional mixins
const withFlying = o => {
let isFlying = false;
return {
...o,
fly () {
isFlying = true;
return this;
},
land () {
isFlying = false;
return this;
},
isFlying: () => isFlying
}
};
const withBattery = ({ capacity }) => o => {
let percentCharged = 100;
return {
...o,
draw (percent) {
const remaining = percentCharged - percent;
percentCharged = remaining > 0 ? remaining : 0;
return this;
},
getCharge: () => percentCharged,
get capacity () {
return capacity
}
};
};
const createDrone = ({ capacity = '3000mAh' }) => pipe(
withFlying,
withBattery({ capacity }),
withConstructor(createDrone)
)({});
const myDrone = createDrone({ capacity: '5500mAh' });
console.log(`
can fly: ${ myDrone.fly().isFlying() === true }
can land: ${ myDrone.land().isFlying() === false }
battery capacity: ${ myDrone.capacity }
battery status: ${ myDrone.draw(50).getCharge() }%
battery drained: ${ myDrone.draw(75).getCharge() }%
`);
console.log(`
constructor linked: ${ myDrone.constructor === createDrone }
`);
console.log('End');
To make it simple, I need to define my own functions like
str.transform1("k", "R").transform2().transform3(0,4);
that do just as
str.replace("k", "R").toUpperCase().substr(0,4);
In reality each function will be a set of complicated replace() function call, but let's make it simple for this question.
What best should I do?

Recursive Callbacks to create a familly tree

I am trying to create a family tree with callback functions nested in callback functions, i'm hoping to get at least 5 generation. the function receive the person's id, which then look for everyone in the database that has the property 'father' with the same id.
This is the function for getting the children of the person
var getChildren=function (person, callback) {
keystone.list('Person').model.find().where('father', person.id).exec(function(err, children) {
callback(children);
})
}
this is how i use the callback function
function getFamilyTree(person){
getChildren(person, function(children){
person.children=children;
for (var i=0;i<person.children.length;i++) {
!function outer(i){
if (isNotEmpty(person.children[i])){
getChildren(person.children[i],function(children){
person.children[i].children=children;
for (var j=0;j<person.children[i].children.length;j++){
!function outer(j){
if (isNotEmpty(person.children[i].children[j])){
getChildren(person.children[i].children[j],function(children){
person.children[i].children[j].children=children;
for (var k=0;k<person.children[i].children[j].children.length;k++){
!function outer(k){
if (isNotEmpty(person.children[i].children[j].children[k])){
getChildren(person.children[i].children[j].children[k],function(children){
person.children[i].children[j].children[k].children=children;
})
}
}(k);
}
})
}
}(j);
}
});
}
}(i);
}
})
}
as you can see, it is very complicated. It works, but sometimes it doesn't retrieve all 5 generation but only 4 or 3, sometimes even 1 and i don't know why, please help my guys, and i'm also a new comer so please be easy with me, thanks in advance!
If you use promises instead of callbacks, you could use a recursive async function to resolve trees of arbitrary depth:
function getChildren(person) {
return new Promise((resolve, reject) => {
keystone.list('Person').model.find().where('father', person.id).exec((err, children) => {
if(err) reject(err);
else resolve(children);
});
});
}
async function getFamilyTree(person, maxDepth, depth=0) {
if(depth >= maxDepth) return person;
const children = (await getChildren(person)).filter(isNotEmpty);
person.children = await Promise.all(
children.map(child => getFamilyTree(child, maxDepth, depth + 1))
);
return person;
}
getFamilyTree({id: 'rootPersonId'}, 5)
.then(tree => console.log(tree))
.catch(error => console.log(error));
You definitely need to use recursion. I don't know exactly how your data looks, or how getChildren works, but this should point you in the right direction:
function getFamilyTree ( person ) {
getChildren( person, function( children ) {
person.children = children;
for ( var i = 0; i < person.children.length ) {
getFamilyTree( person.children[ i ] );
}
})
}
The first thing you can do to simplify the code is to pull out the outer function that you use multiple times throughout the getFamilyTree function so you don't repeat it all over the place. It'll need an update to take the child node as a parameter instead of the index for simplicity.
Since your callback is the same every time, you can pull it out into it's own function. It'll need a slight update to take in the parent item, since you are constantly referencing the person variable throughout the callback.
Your outer function can be something like:
function outer(child) {
if (isNotEmpty(child)) {
getChildren(child, getChildrenCallback);
}
}
And then the getChildrenCallback will be:
function getChildrenCallback(children, parent) {
parent.children = children;
for ( var i = 0; i < children.length; i++) {
outer(child[i]);
}
}

Defining an indexer for an object

One can make an object iterable by implementing [Symbol.iterator].
But how can one override the behavior of the [] operator?
For example i have a an object which has an array inside of it and i want to be able to access that given an index like obj[3].
is that possible?
example
const SignalArray = (data = []) => {
...
return {
add,
remove,
onAdd,
onRemove,
onSort,
removeOnAdd,
removeOnRemove,
removeOnSort,
[Symbol.iterator]() {
return {
next: () => {
if (index < data.length) {
return { value: data[index++], done: false };
} else {
index = 0;
return { done: true };
}
}
}
}
}
}
how can one override the behavior of the [] operator?
Only via Proxy, added in ES2015. You'd provide a get trap and handle the property keys you want to handle.
Here's an example where we check for property names that can be successfully coerced to numbers and return the number * 2:
const o = new Proxy({}, {
get(target, prop, receiver) {
const v = +prop;
if (!isNaN(v)) {
return v * 2;
}
return Reflect.get(...arguments);
}
});
o.x = "ex";
console.log(o[2]); // 4
console.log(o[7]); // 14
console.log(o.x); // "ex"
If you want to override setting the array element, you'd use set trap. There are several other traps available as well. For instance, in a reply to a comment, you said:
...if you hate es6 classes and want to write a wrapper around an array that gives it extra functionality, like an observable array for example...
...that would probably involve a set trap and overriding various mutator methods.

Trying to understand functor but did not understand what that code exactly doing

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)"

Categories

Resources