Function parameter type in JavaScript - javascript

I have a function to loop over an Array and look for a number.
function search(arr, num) { }
This function takes an Array and an Integer as the input. But I want the parameters to be of their respective types, is there any way I can set the type of the parameters.

if you want to enforce an specific data type you need to explicitly write that
/**
* looks for an specific item of the list.
*
* #param {Array<string>} data Array to search from.
* #param {number} num Number of where to find bla bla.
* #throws {TypeError} in case data or num do not have the expected type
* #returns {string} item found.
*/
export function search(data, num) {
if (!Array.isArray(data)) {
throw new TypeError("data should be an array");
}
if (typeof num !== "number") {
throw new TypeError("num should be a number");
}
return data[num];
}

JavaScript is a loosely typed language, there's no way to say declaratively that arr should be an array and num should be a number other than documentation (for instance, JSDoc). That would look like this:
/**
* Searches (insert fuller description here).
*
* #param {number[]} arr The array to search through.
* #param {number} num The number to (use? search for?)
* #returns (you can use the same {syntax} to say what the return type is)
*/
function search(arr, num) {
if (!Array.isArray(arr)) {
throw new Error(`'arr' argument must be an array`);
}
if (typeof num !== "number"/* Or use `Number.isInteger(num)` for an integer check [it includes the typecheck]*/) {
throw new Error(`'num' argument must be a number`);
}
// ...do the work...
}
You could check at runtime. That would look something like this:
function search(arr, num) {
if (!Array.isArray(arr)) {
throw new Error(`'arr' argument must be an array`);
}
if (typeof num !== "number"/* && perhaps a check for int if that part is really important*/) {
throw new Error(`'num' argument must be a number`);
}
// ...do the work...
}
There's a language built on top of JavaScript called TypeScript, which adds a static type system. It's then compiled to JavaScript for use with the browser, Node.js, etc. You may want to look into using that, if static typing is important for what you're doing.
In TypeScript, you'd do it like this (if you want the array to be an array of numbers):
function search(arr: number[], num: number) {
// ...
}
That doesn't require an integer, all JavaScript numbers are IEEE-754 floating point or BigInts.

javascript is a loosely type language. beacuse of it there are no types, you can actively check if the given parameter is array by .isArray() and .isInteger(), this both methods will return the booleen.
the answer given by pravat work but it can be devasting as they will not only set the type its will set its default value, in case if you didn't send the value of num then function will asume its 10 and start to work, which can be pain in debuging if you need strict type please look into typescript.

Related

Receive 2394 in typescript

Why am I getting the error?
I tried various methods, but I did not get the result and this error was not fixed
my code:
function $sum(xor:string[]):string
function $sum(xor:number[]):number
{
let xor_;
if(xor instanceof String){
for(let i of xor){
xor_+=i;
}
}else{
for(let i of xor){
xor_+=i;
}
}
return xor_;
}
my error:
As the error mentions, you must provide a valid implementation signature:
Info from the typescript docs
Stackblitz example:
function $sum(xor:string[]):string;
function $sum(xor:number[]):number;
function $sum(xor:Array<string | number>):number | string
{
/**
* as per your overloads the function must return
* - a string when the input is a string array
* - a number when the input is a number array
*
* To determine if the input is an array of string or an array of number
* you could check the type of the first array item
* i.e. typeof xor[0] === 'string'
*
* as T.J.Crowder pointed out in a comment,
* this is not possible when you get an empty array: so your
* function must not allow empty arrays: i.e. throw an error
*/
}
the first 2 lines are the overloads, the 3rd is the implementation signature.

JS Object array property initializer / optional chaining

To initialize an Object array property and then push values seems to be a two step approach. Is there an optimal one line approach in ES6? Example of the issue is in the appendError(prop, error) method below.
Question:
Is there one line or more concise approach with JS or lodash?
Future optional chaining seems solve the problem, but is there a solution today? PHP allows $array[prop][] = val;
class Errors {
constructor(fields ) {
this.errors = {};
}
/**
* Clear one or all error fields.
*
* #param {string} prop | example: "email"
* #param {string} error | example: "Invalid email"
*/
appendError(prop, error) {
/* Set key and empty array if property doest exist */
if (!this.has(prop)) this.errors[prop] = [];
/* then Push value */
this.errors[prop].push(error);
}
/**
* Determine if an errors exists for the given field.
*
* #param {string} prop
*/
has(prop) {
return this.errors.hasOwnProperty(prop);
}
}
You can use Logical nullish assignment (??=) to assign an empty array, if the property value id undefined:
(this.errors[prop] ??= []).push(error);
Example:
class Errors {
constructor(fields) {
this.errors = {};
}
appendError(prop, error) {
(this.errors[prop] ??= []).push(error);
}
}
const errors = new Errors;
errors.appendError('a', 'a');
errors.appendError('a', 'b');
console.log(errors.errors);
You could abuse || for that:
this.errors[prop]?.push(error) || (this.errors[prop] = [error]);
You could write something like:
appendError(prop, error) {
(this.error[prop] = this.error[prop]||[]) && this.error[prop].push(error) && this.error[prop];
}

type checking helper with Ramda

I want to write a function whose specifications is described in the piece of code below which is the current implementation I have. It does work. However I have been trying for a while to write it pointfree and entirely as a composition of ramda functions and could not find a solution. The issue is linked to obj => map(key => recordSpec[key](obj[key]) which I cannot reduce in a way that I can write the whole thing pointfree.
How could I do?
/**
* check that an object :
* - does not have any extra properties than the expected ones (strictness)
* - that its properties follow the defined specs
* Note that if a property is optional, the spec must include that case
* #param {Object.<String, Predicate>} recordSpec
* #returns {Predicate}
* #throws when recordSpec is not an object
*/
function isStrictRecordOf(recordSpec) {
return allPass([
// 1. no extra properties, i.e. all properties in obj are in recordSpec
// return true if recordSpec.keys - obj.keys is empty
pipe(keys, flip(difference)(keys(recordSpec)), isEmpty),
// 2. the properties in recordSpec all pass their corresponding predicate
// For each key, execute the corresponding predicate in recordSpec on the
// corresponding value in obj
pipe(obj => map(key => recordSpec[key](obj[key]), keys(recordSpec)), all(identity)),
]
)
}
For instance,
isStrictRecordOf({a : isNumber, b : isString})({a:1, b:'2'}) -> true
isStrictRecordOf({a : isNumber, b : isString})({a:1, b:'2', c:3}) -> false
isStrictRecordOf({a : isNumber, b : isString})({a:1, b:2}) -> false
One way to achieve this would be to use R.where, which takes a spec object like your recordSpec and applies each predicate with the value from the corresponding keys of the second object.
Your function would then look like:
const isStrictRecordOf = recordSpec => allPass([
pipe(keys, flip(difference)(keys(recordSpec)), isEmpty),
where(recordSpec)
])

Google closure: trouble type checking parameters that should be functions

I'm messing around with the type checking in google's closure compiler. The type system seems useful, if perhaps not the most sophisticated out there. I'm happy with most of the limitations, but this one just seems a bit weird.
I'm seeing problems giving type annotations for functions passed as arguments. In particular, if the type of the passed function is itself not fixed. So for example, I'd like to write code similar to this:
/**
* #param {Array} xs
* #param {function(*) : boolean} f
* #return {Array}
*/
var filter = function (xs, f) {
var i, result = [];
for (i = 0; i < xs.length; i += 1) {
if (f(xs[i])) {
result.push(v);
}
}
return result;
};
filter([1,2,3], function (x) { return x > 1; });
Passing "--js_error checkTypes" to the compiler, I get this:
test.js:17: ERROR - left side of numeric comparison
found : *
required: number
filter([1,2,3], function (x) { return x > 1; });
^
So, what's wrong? Can I specify that a parameter ought to a function with one argument, without specifying the type of that argument? Am I doing something wrong, or is this just a limitation of the type checker?
Chad suggests annotating the anonymous function passed to filter to help the type-inference out a bit:
filter([1,2,3], function (x) { return /** #type {number} */ (x) > 1; });
That works ok for filter(), but it seems a little unsatisfying (why does the compiler need that annotation?), and doesn't work for more complex cases. For example:
/**
* #param {Array|string} as
* #param {Array|string} bs
* #param {function(*, *): *} f
* #return {Array}
*/
var crossF = function (as, bs, f) {};
/**
* #param {Array|string} as
* #param {Array|string} bs
* #return {Array}
*/
var cross = function (as, bs) {};
var unitlist = crossF(['AB', 'CD'], ['12', '34'], cross);
It seems like the type of everything here should be apparent to the compiler. And in fact it complains directly about matching the type of the function parameter:
test.js:52: ERROR - actual parameter 3 of crossF does not match formal parameter
found : function ((Array|null|string), (Array|null|string)): (Array|null)
required: function (*, *): *
var unitlist = crossF(['ABC', 'DEF', 'GHI'], ['123', '456', '789'], cross);
Accepted answer below addresses this case.
Change the declaration of the filter from "*" (everything) to "?" (unknown). The compiler only checks known types. So when the compiler tries to infer the function signature for the function expression at the call site, it resolves the parameter "x" to "?" (an unknown type) (which can be used as anything), instead of "*" (every possible type) which often needs to be restricted before use:
/**
* #param {Array} xs
* #param {function(?) : boolean} f
* #return {Array}
*/
var filter = function (xs, f) {
var i, result = [];
for (i = 0; i < xs.length; i += 1) {
if (f(xs[i])) {
result.push(v);
}
}
return result;
};
When there are no annotations on a function, the compiler assumes that it can take a variable number of arguments of any type and return any type. For this reason, many of the extern functions are annotated like this:
/** #return {undefined} */
function MyFunction() {}
This way they will properly type check.
For your case, the easiest solution is to type cast the argument to a number inside the function (note the extra parenthesis which are required):
filter([1,2,3], function (x) { return /** #type {number} */ (x) > 1; });
One common approach is to use the type annotation {!Function}, which accepts any function object.
The issue with the ALL type (*) has been reported here: Issue 708
This looks like a bug to me. You should file it here:
http://code.google.com/p/closure-compiler/issues/list
If you don't specify the types, it should be "unknown"(?) not "any"(*). The compiler doesn't (or shouldn't) type check the use of unknown types.

How to convert instance of any type to string?

I'm implementing a function that receives an argument which it needs to convert to its string representation.
If a given object implements a toString() method, then the function should use it. Otherwise, the function can rely on what the JavaScript implementation offers.
What I come up with is like this:
var convert = function (arg) {
return (new String(arg)).valueOf();
}
String(null) returns - "null"
String(undefined) returns - "undefined"
String(10) returns - "10"
String(1.3) returns - "1.3"
String(true) returns - "true"
I think this is a more elegent way.
I'm not sure you even need a function, but this would be the shortest way:
function( arg ) {
return arg + '';
}
Otherwise this is the shortest way:
arg += '';
value = value+"";
If targeting ES6 or later, you could use a template literal:
function (arg) {
return `${arg}`;
}
All data types in JavaScript inherit a toString method:
('hello').toString(); // "hello"
(123).toString(); // "123"
([1,2,3]).toString(); // "1,2,3"
({a:1,b:2}).toString(); // "[object Object]"
(true).toString(); // "true"
These are all great answers, but here comes the ultimate solution that covers all the cases :)
function stringify(value) {
switch (typeof value) {
case 'string': case 'object': return JSON.stringify(value);
default: return String(value);
}
};
for (const value of [
null,
undefined,
12345,
'abcdef',
true,
false,
BigInt(9007199254740991),
NaN,
Symbol('sym'),
[1, 2, 3],
{aaa: 'AAA', bbb: 'BBB'},
(a, b) => a + b,
stringify,
]) {
console.log(stringify(value));
}
The other answers are incomplete when it comes to a JSON object passed. So I made this one and it works for all:
var getString = (o) => {
if (o !== null) {
if (typeof o === 'string') {
return o;
} else {
return JSON.stringify(o);
}
} else {
return null;
}
}
JSON.stringify(value)
Works for null, undefined, primitives, arrays and objects — basically everything.
/* use back tic; same key as tilde(~) typically, and ${} */
const myString = `${value}`;
Short way to convert most types to String, but some types such as Objects will stringify their type [object Object]. For those
/**
* #typedef {Object} replacerProps
* #property {any} this - the replacer function.
* #property {string} key - the key of the current node passed to the replacer.
* #property {any} value - the data of the current node passed to the replacer.
*/
/**
* #param {any} value - data to stringify.
* #param {((replacerProps) => any)|undefined} [replacer=null] - A function
* that alters the behavior of the stringification process, or an array
* of String and Number that serve as an allowlist for selecting/filtering
* the properties of the value object to be included in the JSON string. If
* this value is null or not provided, all properties of the object are
* included in the resulting JSON string.
* #param {string|number} [space=null] - insert whitespace for readability.
* If null, no whitespace is used.
* #returns {string|undefined} a JSON string representing the given
* value or undefined.
*/
JSON.stringify(value, null, 0);
JSON.stringify() is the best catch-all solution.

Categories

Resources