An open API service indexing awesome lists of open source software.

https://github.com/amekusa/flex-params

Multiple signatures for a function
https://github.com/amekusa/flex-params

function function-signatures npm overloading parameters

Last synced: 2 months ago
JSON representation

Multiple signatures for a function

Awesome Lists containing this project

README

        

- **v3.1.0**
- Support simpler syntax
- Support pipe separated multiple types
- **v3.0.1** [Fix] Broken ES module loading since 3.0.0 has been fixed.
- **v3.0.0** The error handling API changed.

---

[![Build Status](https://travis-ci.com/amekusa/flex-params.svg?branch=master)](https://travis-ci.com/amekusa/flex-params) [![npm](https://img.shields.io/badge/dynamic/json?label=npm%0Apackage&query=%24%5B%27dist-tags%27%5D%5B%27latest%27%5D&url=https%3A%2F%2Fregistry.npmjs.org%2Fflex-params%2F)](https://www.npmjs.com/package/flex-params)

**flex-params** is a tiny, but powerful utility for writing a function that has **multiple signatures**.

As you know, JavaScript doesn't support **function overloading** like this:
```js
function foo(X) { /* ... */ }
function foo(X, Y) { /* ... */ } // This makes the 1st foo() not callable
```

Yes, still you can do the same kind of thing with default parameters most of the time. But if you want more flexibility or scalability, flex-params allows you to define **multiple combinations of parameters** for a single function.

- [Getting Started](#getting-started)
- [Usage](#usage)
- [Defining a pattern of parameters](#defining-a-pattern-of-parameters)
- [Default Values](#default-values)
- [Let's see how it works](#lets-see-how-it-works)
- [More Example](#more-example)
- [Advanced Usage](#advanced-usage)
- [Error Handling](#error-handling)
- [Appendix: Extra Types](#appendix-extra-types)

## Getting Started
Install it with NPM:
```sh
npm i flex-params
```

And `require()` it:

```js
const flexParams = require('flex-params');
```

or `import` it as an ES module:

```js
import flexParams from 'flex-params';
```

## Usage

In your function, pass an array of the arguments to `flexParams()` with any number of **patterns of parameters (a.k.a. "signatures")** you desired.

```js
// Example Code
function foo(...args) {
var result = {};

flexParams(args, [
{ X:'string', Y:'int' }, // pattern #0
{ X:'string', Z:'bool', Y:'int' }, // pattern #1
{ Z:'bool', Y:'int', X:'string' }, // pattern #2
... // more patterns
], result);

return result;
}

var r = foo('blah', 42); // { X:'blah', Y:42 }
var r = foo('blahh', true, 7); // { X:'blahh', Y:7, Z:true }
var r = foo(false, 11, 'blaahh'); // { X:'blaahh', Y:11, Z:false }
```
In the code above, `args` is the array of arguments.

The 2nd parameter of `flexParams()` is an array of the *patterns*.
`flexParams()` tries to find **the most suitable pattern** in the array for `args` by comparing the type strings defined in each pattern with actual types of `args`.

Once it is found, each value of `args` is stored into `result` ( the 3rd parameter ) as its properties.

If you prefer simpler syntax, you can also write like this:

```js
var result = flexParams(args, [
... // patterns
]);
```

### Defining a pattern of parameters
Each pattern must be a plain object that has one of some specific formats.
The most basic format is like this:

```js
// Pattern Definition
{
param_1st: '',
param_2nd: '',
...
param_nth: ''
}
```

`''` is a string representation of *datatype* (ex. `'bool'`, `'string'`, `'array'`, `'object'`, etc. ) for each param, like this:

```js
{ foo:'string', bar:'boolean' }
```

This pattern means:
- The 1st param is `foo`, and it must be a string
- The 2nd param is `bar`, and it must be a boolean

You can also write **multiple types** separated by `|` (pipe) likes this:

```js
{ foo:'string|number|boolean' }
```

This parameter matches with a string, a number or a boolean.

#### Default Values
Instead of just a type string, you can also use an **array** to define the default value:

```js
{ foo:'string', bar:['boolean', false] }
```

Now the 2nd param `bar` turned to **optional**. The default value is `false`.

The pattern that contains optional parameters can be considered suitable even if **a fewer number of arguments** supplied. And the missing arguments will be filled with the default values of respective params.

### How does it work?
```js
// Example Code #1
function foo(...args) {
let result = {};

flexParams(args, [
{ flag:['boolean', false] }, // pattern #0
{ str:'string', num:['number', 1] }, // pattern #1
{ num:'number', flag:'boolean' } // pattern #2
], result);

return result;
}
```

You can see 3 patterns in the above example.
Let's test it by passing various combinations of arguments:
```js
let test1 = foo(); // No argument
let test2 = foo('ABC'); // A string
let test3 = foo(8, true); // A number, A boolean

console.log('Test 1:', test1);
console.log('Test 2:', test2);
console.log('Test 3:', test3);
```

And these are the resulting objects:
```js
Test 1: { flag: false }
Test 2: { str: 'ABC', num: 1 }
Test 3: { num: 8, flag: true }
```

Now you can see:
- The test1 matched with pattern #0
- The test2 matched with pattern #1
- The test3 matched with pattern #2

### More Example
```js
// Example Code #2
const flexParams = require('flex-params');

class User {
constructor(...args) {
flexParams(args, [
// patterns
{ firstName:'string', age:'int' },
{ firstName:'string', lastName:'string', age:'int' },
{ id:'int' },
{ login:'string', pass:Password }

], this); // Stores the args into 'this'
}
}

class Password {
constructor(key) {
this.key = key;
}
}

//// Test ////////////
let thomas = new User('Thomas', 20);
let john = new User('John', 'Doe', 30);
let user1 = new User(1000);
let user2 = new User('d4rk10rd', new Password('asdf'));

console.log(thomas);
console.log(john);
console.log(user1);
console.log(user2);
```

Console outputs:
```js
User { firstName: 'Thomas', age: 20 }
User { firstName: 'John', lastName: 'Doe', age: 30 }
User { id: 1000 }
User { login: 'd4rk10rd', pass: Password { key: 'asdf' } }
```

As you can see this example, you can pass `this` to the 3rd parameter of `flexParams()`. This way is useful for **initializing the instance** in the class constructor.

### Advanced Usage

About the 3rd parameter of `flexParams()`, it accepts not only an object but also a **function**.

```js
function foo(...args) {
flexParams(args, [
{ flag:['boolean', false] }, // pattern #0
{ str:'string', num:['number', 1] }, // pattern #1
{ num:'number', flag:'boolean' } // pattern #2

], (result, pattern) => { // Receiver Callback
console.log('result:', result);
console.log('pattern:', pattern);
});
// Test ////////
foo('XYZ', 512);
}
```

Console outputs:

```js
result: { str: 'XYZ', num: 512 }
pattern: 1
```

Let's call this function a **receiver callback**. Receiver callback runs immediately after `flexParams()` finished processing `args`.

Receiver callback takes 2 parameters: `result` and `pattern`.
`result` is an object that contains all the `args` as its properties. `pattern` is **the index number of the matched pattern**, which is `1` ( means pattern #1 ) at this time.
This index is useful if you want to do some different things for each pattern with `switch-case` or `if-else-if`, like this:

```js
function foo(...args) {
return flexParams(args, [
{ flag:['boolean', false] }, // pattern #0
{ str:'string', num:['number', 1] }, // pattern #1
{ num:'number', flag:'boolean' } // pattern #2

], (result, pattern) => { // Receiver Callback
switch (pattern) { // Do stuff for each pattern
case 0: return 'The first pattern matched.';
case 1: return 'The second pattern matched.';
case 2: return 'The last pattern matched.';
}
});
}
//// Test ////////
console.log( foo('XYZ', 512) ); // 'The second pattern matched.'
console.log( foo(65535, false) ); // 'The last pattern matched.'
console.log( foo() ); // 'The first pattern matched.'
```

### Error Handling

If all the given patterns didn't match for the arguments, `flexParams()` returns `false`. But there is also another way to handle this situation. **The 4th parameter: `fallback`**.

`fallback` can be:

- a) **a callback**,
- b) **an object**, or
- c) **any other type of value** ( except for `undefined` ).

a) If you passed a callback, it is called when all the given patterns mismatched.
Tha callback receives **an object** as its parameter with these properties:
- **args**: The original array of arguments
- **patterns**: The array of all the patterns
- **receiver**: The receiver object or function passed to the 3rd parameter
- **error**: An Exception object ( `flexParams.InvalidArgument` )

b) You can pass an plain object as the `fallback` with these optional properties:
- **log**: A string to be sent to `console.log()`
- **warn**: A string to be sent to `console.warn()`
- **error**: A string to be sent to `console.error()`
- **throw**: Any type of value to be thrown as an exception
- If you set `true` , an Exception object is thrown ( `flexParams.InvalidArgument` )

c) Any other type of value as the `fallback` , is returned straightly by `flexParams()` if all the given patterns mismatched.

## Appendix: Extra Types

flex-params supports some special types in addition to [JavaScript's builtin datatypes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures).

| Type | Description |
|-----:|:------------|
| `bool` | Alias of `boolean` |
| `int`, `integer` | Matches for an integer |
| `float`, `double` | Alias of `number` |
| `array` | Matches for an array |
| `iterable` | Matches for an array or an array-like object |
| `primitive` | Matches for a primitive type value |
| `any`, `mixed` | Matches for any type of value |
| A class constructor | Matches for the class instances |

---

© 2020 [amekusa](https://amekusa.com)