If I want to define multiple symbols in a row, the syntax is a little verbose:
const a = Symbol('a'),
b = Symbol('b'),
c = Symbol('c');
I came up with a slightly more concise way to do it:
const [a, b, c] = ['a','b','c'].map(key => Symbol(key))
Is that the most concise way possible, or is there some dedicated syntax for declaring multiple symbols that I'm unaware of?
P.S. 'a' 'b' and 'c' are just arbitrary examples of course. I realize that one could get clever with this particular example ;)
There's a fun part of FP that you might be missing out on, which is that in a pure system, you can always replace an anonymous function with a named reference which is returned (and more generally, you can always replace a function with the value it would have returned), assuming that the function you're replacing and the function you're replacing it with take the same number of arguments.
const [a, b, c] = ["a", "b", "c"].map(Symbol);
Should work just fine, as long as you keep in mind that you can cause accidents here, by passing in functions that expect more than 1 argument.
Sure, not much more terse than what you already had. ...but still more than none.
It can be as concise as
const [a, b, c] = ['a','b','c'].map(Symbol);
There's no way to skip abc tautology here because variable names should be explicitly written, as well as symbol description.
Symbol descriptions are helpful for debugging. If they can be omitted (not recommended), it becomes
const [a, b, c] = [,,].map(Symbol);
for fixed amount of variables. And
const [a, b, c, d /*, ... */] = function* () { for(;;) yield Symbol() }();
for unlimited amount of variables.
One option, if you don't mind using ES6-only features that can't be polyfilled, is to use a Proxy that returns a symbol for each property access. You could make a helper module file like
module.exports = function autoSymbols() {
return new Proxy({}, {
get(target, name, receiver){
return Symbol(name);
},
});
}
then do
const {a, b, c} = require('./auto-symbols');
though personally I think enumerating the Symbol instances manually is more easily maintained.
Related
I have a simple function as follow:
const transform = ([a, b]) => ({operation: a, ...b});
How can I type this function to make sure the return type is an object containing a key operation with the type of the argument a and the property of b.
I tried the following but without success:
function transform<[A, B]>(param: [A, B]): { operation: A } & B {
return { operation: param[0], ...param[1] };
}
Any help about that? Thanks!
You have what amounts to a typographical error; instead of transform<[A, B]>(...), you need to write transform<A, B>(...) like this:
function transform<A, B>(param: [A, B]): { operation: A } & B {
// ----> ^^^^^^ ^ ^ <------------> ^ ^
// declaring type params using the type params
//
return { operation: param[0], ...param[1] };
}
The transform() function is generic in two type parameters A, and B. (You can think of these almost like regular function parameters, but instead of corresponding to arguments that callers pass in as values, they are arguments that callers pass in as types.) The syntax for declaring the list of type parameters a generic function takes is to put a comma-separated list of these parameters inside angle brackets, immediately before the list of regular parameters in parentheses.
It's only later in the function signature and implementation where you use the type parameters as types. So the param function parameter is annotated as type [A, B], a tuple type where the first element is of type A and the second element is of type B. The fact that you were going to use the type [A, B] has nothing to do with the syntax to declare the type parameters.
That's the answer to the question as asked.
I could go a little further and suggest that a more direct translation of your original transform JavaScript code into TypeScript would look like this:
const transform = <A, B extends object>(
[a, b]: [A, B]
): { operation: A } & B => ({ operation: a, ...b });
Here you can see that even arrow functions can be generic, and therefore need a type parameter declaration before the parameter list (which must be in parentheses, even if your arrow function looks like x => x... you can't write <T>x:T => x, you need <T>(x: T) => x). Also see that the return type annotation of an arrow function comes immediately after the parameter list with a colon, and before the arrow =>.
Since you are going to use object spread on b which of type B, it's useful to tell the compiler that B should be constrained to the object type, so that nobody calls transform([1, 2]) and is possibly confused by what comes out.
Also, you can still use destructuring assignment in your function parameter, so we have ([a, b]: [A, B]) instead of (param: [A, B]).
Playground link to code
Background:
Composition is putting two functions together to form a third function where the output of one function is the input of the other.
No matter how much I look at this I struggle with how to read it. In particular why the compose() return => (a) => captures the 121.2121212 in local scope. Also I struggle with how final fn f(g(a)) would look with all the values/fn present w/o the use of variables.
Question: Does anyone have any techniques or diagrams for quickly reading examples like this; how can I mentally debug and follow the function flow?
Reference:
const compose = (f, g) => (a) => f(g(a)) // Definition
const floorAndToString = compose((val) => val.toString(), Math.floor) // Usage
floorAndToString(121.212121) // '121'
As mentioned by T.J. Crowder, it often helps rewriting arrow functions as regular functions. So the function:
const compose = (f, g) => (a) => f(g(a))
Can be rewritten as:
function compose (f, g) {
return function (a) {
return f(g(a));
}
}
Now it is perhaps more obvious what's going on. So now let's rewrite the other parts:
const floorAndToString = compose((val) => val.toString(), Math.floor)
Can be rewritten as:
function convertToString (val) { return val.toString() };
const floorAndToString = compose(convertToString, Math.floor);
Now it may be more obvious that the compose function will return the function:
// remember that we pass `convertToString` as `f`
// and `Math.floor` as `g`:
function (a) {
return convertToString(Math.floor(a));
}
So it's obvious that the function floorAndToString simply returns the result of convertToString(Math.floor(a)). There is nothing special about compose that captures 121.2121212 because it doesn't. Instead it creates a function where 121.2121212 can be passed as an argument to convertToString(Math.floor(a)).
It might help to look at the Wikipedia article for function composition. But I think your problem is not really related to function composition but to the arrow notation in general.
Maybe it helps to look at a simpler example first:
const addOne = (x) => x + 1
const addN = (n) => (x) => x + n
const addSeven = addN(7)
The last line produces a new function that adds seven to the input (x) => x + 7. You can think of the parameter tuples between the arrows as being filled from left to right when values are provided (and the variables to the right are bound to these values). As long as you don't provide all parameters, you will obtain a new function.
You can also provide all parameters like this:
addN(5)(3) // yields 8
Note that addN can be seen as taking two parameters but in separate bracket pairs. The arrows between the brackets in the definition kind of allow you to omit parameters to the right and obtain a function with fewer parameters with the left ones being already fixed.
Let's look at an alternative definition of compose:
const weirdCompose = (f, g, a) => f(g(a))
It should be clear how it works, but the problem is that you cannot use this to compose two functions without evaluating the result of the composition with the value a right away. By separating the parameters into two groups you can partially apply the function and only provide f and g in a first step.
To understand this better, I suggest you also have a look at the concept of currying
My problem is best explained with a simple code example:
function doThing(a, ...b, c) {
console.log(a, b, c)
}
doThing(1,2,3,4); // Expect "1 [2,3] 4"
This instead gives a syntax error Unexpected token, pointing to the comma after the b in the function definition.
Is it not possible to put a single parameter after a 'rested' parameter with ES6? If not, any idea why? It would be really useful for me. Thanks!
Edit: I initially thought that doThing(a, ...b, c) would be completely unambiguous, but I see now that doThing(1,2,3) would need an arbitrary rule to decide whether the 3 goes in b or c (i.e. if we pass a number of params that is less than or equal to the number of params in the function definition).
developer.mozilla.org says:
If the last named argument of a function is prefixed with ..., it becomes an array whose elements from 0 to theArgs.length are supplied by the actual arguments passed to the function.
Actually this feature of ES6 is called Rest parameters so it is meant to be the last in the list of parameters.
So this code will work:
function doThing(a, b, ...c) {
console.log(a, b, c);
}
doThing(1,2,3,4);
I'm sure this thing is duplicated somewhere but I don't know what to search.
So, I've been looking through a Node.JS Application and found this code and wondered what it does. I have tried searching but I don't know what to search so I was hoping someone would it explain it to me.
init = refresh = function () {
// code here..
};
I understand 1 equals, but why 2? does it make some sort of alias so that function can be run with both init and refresh?
= resolves the right hand side and then assigns the result to the left hand side.
The result of doing this is the same as the result assigned.
So that assigns the function to both init and refresh
Quentin did a very good job telling you what it is doing.
I just wanted to chime in to give an example where you might use this:
Say for instance you have an object:
var obj = {
init: function() {
var x = this.x = [1,2,3];
}
};
What this allows you to do is reference your x variable two different ways (either through x or this.x).
Now why would you do this?
Well two major reasons.
It is faster to access x rather than this.x (but you still need to access it elsewhere)
It produces easier to read code when having to read/write to x lots of times in one function.
This is just another reason why you would use it.
But in most cases it is just aliases, such as: forEach -> each
Here's an explanation using operator associativity and precedence.
So, looking at an operator precedence description from Mozilla, when an expression includes multiple operators of the same precedence, as in
a OP b OP c
, then you check whether that level of precedence uses right-to-left or left-to-right associativity.
a = b = c
The assignment operator in JavaScript is the only operator on its level of precedence.
It has right-to-left associativity
So in a = b = c, b = c is evaluated first, assigning the value of c to b.
Then the expression becomes a = b.
I mean this more exactly:
var MyClass = function(){
return {
init: function(a, b, c){
this.__a = a;
this.__b = b;
this.__c = c;
}
,doSomething: function(){
// when to use this:
this.__subThingType1();
// or this?
this.__subThingType2(this.__a, this.__b, this.__c);
}
,__subThingType1: function(){
var a = this.__a;
var b = this.__b;
var c = this.__c;
// do dirty things with a, b and c
}
,__subThingType2: function(a, b, c){
// do dirty things with a, b and c
}
}
When should I use the type1 or type2? I cannot figure out a rule for this, I find my code that there is a mix between the two, because sometimes I think I will remove that method from this class and put it in a lib.
But is not a good rule, because sometime I leave the method in the class, so a lot of mixing happens.
Also, the method should return a value or alter the instance variables?
Thanks
Store intrinsic data in the instance; pass in extrinsic data as arguments. For example, your class represents a circle. Store its center and radius as intrinsic data. But your method intersectionWith takes an argument anotherCircle and returns the area of intersection.
The difference between intrinsic and extrinsic is not always so obvious, but coding is not always obvious. Use your judgement.
More generally, if you're puzzled by this kind of thing, you probably want to learn more about the paradigm of Object-oriented Programming.