@ rows/ 多方法
目录
导言
多方法是具有超能的功能——它们可以做所有普通的功能所能做的,但另外还有:
- 可在提供的论点基础上选择适当的执行,而不明确附加条件的逻辑,
- 很容易扩展,不需要修改原始代码,
- 允许您写出干净、简洁和解码.
多方法库提供了一套微小的更高级的功能来创建强大的,不可变的多方法——在功能上.
图书馆有 内置类型定义,提供了极佳的IDE支持.
安装
通过国家预防机制:
npm i @arrows/multimethod
维亚·亚恩:
yarn add @arrows/multimethod
使用量
注意: 您可以在 实例 文件夹.
快速实例
import { multi, method, fromMulti } from '@arrows/multimethod'
/**
* Save data in a specified format
*
* @param {object} data
* @param {string} format
* @returns {void}
*/
const save = multi(
(data, format) => format, // Custom dispatch function
method('json', (data, format) => {
console.log('Saving as JSON!')
}),
method('html', (data, format) => {
console.log('Saving as HTML!')
}),
method((data, format) => {
console.log('Default - saving as TXT!')
}),
)
const data = { name: 'Alice', score: 100 }
save(data, 'json') // -> "Saving as JSON!"
save(data, 'html') // -> "Saving as HTML!"
save(data, 'csv') // -> "Default - saving as TXT!"
让我们添加一个新的格式,而不触及现有的代码:
const extendedSave = method('csv', (data, format) => {
console.log('Saving as CSV!')
})(save)
extendedSave(data, 'json') // -> "Saving as JSON!"
extendedSave(data, 'html') // -> "Saving as HTML!"
extendedSave(data, 'csv') // -> "Saving as CSV!"
extendedSave(data, 'yaml') // -> "Default - saving as TXT!"
我们还可以很容易地以多种方法来扩展原始功能:
const extendedSave2 = fromMulti(
method('csv', (data, format) => {
console.log('Saving as CSV!')
}),
method('yaml', (data, format) => {
console.log('Saving as YAML!')
}),
)(save)
extendedSave2(data, 'json') // -> "Saving as JSON!"
extendedSave2(data, 'html') // -> "Saving as HTML!"
extendedSave2(data, 'csv') // -> "Saving as CSV!"
extendedSave2(data, 'yaml') // -> "Saving as YAML!"
在这两种情况下,原件为: save 函数保持不变.
这只是个简单的例子,你可以做更多的事情!
多方法解剖
const myFunction = multi(
optional_dispatch_function,
method(optional_case_value, corresponding_value),
...other_methods,
)
调度职能
调度函数产生值,应当通过该值发出多方法. 将调度函数的返回值与已登记方法的大小写值进行比较.
匹配算法使用深度严格的等同来找到匹配. 该规则有几种例外,在下文说明 大小写值 节.
说明: 目前执行的深度严格等同算法保证了JSON兼容型的正确结果, Map, (中文(简体) ), Set,以及 TypedArray
如果不提供发送函数,则使用默认的函数。 默认调度函数将所有参数作为一个数组返回,或者在单个参数的情况下——作为一个独立值返回.
为了收集有用的、随时可用的调度功能,请检查 @ arrows/ 发送 软件包.
实例:
/**
* Function with a single argument,
* default dispatch will return that argument as-is.
*
* @param {string} color
* @returns {string} hex color
*/
const colorToHex = multi(
method('red', '#ff0000'),
method('green', '#00ff00'),
method('blue', '#0000ff'),
)
colorToHex('green') // -> "#00ff00"
/**
* Function with multiple arguments,
* default dispatch will return the array of arguments.
*
* Note that the order of the arguments does matter,
* so if you want the example to work for each combination,
* you either have to provide methods for reversed arguments
* or add your custom dispatch function,
* which returns sorted values (better option).
*
* @param {string} colorA
* @param {string} colorB
* @returns {string} the resulting color
*/
const mixLights = multi(
method(['red', 'green'], 'yellow'),
method(['red', 'blue'], 'magenta'),
method(['green', 'blue'], 'cyan'),
)
mixLights('red', 'blue') // -> "magenta"
mixLights('blue', 'red') // -> throws an error
/**
* Function with a custom dispatch.
* More flexible version of the previous example.
*
* @param {string} colorA
* @param {string} colorB
* @returns {string} the resulting color
*/
const mixLights = multi(
(colorA, colorB) => [colorA, colorB].sort(), // custom dispatch
method(['green', 'red'], 'yellow'),
method(['blue', 'red'], 'magenta'),
method(['blue', 'green'], 'cyan'),
)
mixLights('red', 'blue') // -> "magenta"
mixLights('blue', 'red') // -> "magenta"
const store = {
add(text) {
console.log('todo added')
},
remove(id) {
console.log('todo removed')
},
toggle(id) {
console.log('todo toggled')
},
}
/**
* Function with a custom dispatch.
* Dispatch function can produce any arbitrary value.
*
* @param {Object} action
* @param {Object} store
* @returns {void}
*/
const handleAction = multi(
(action, store) => action.type, // custom dispatch
method('ADD_TODO', (action, store) => store.add(action.text)),
method('REMOVE_TODO', (action, store) => store.remove(action.id)),
method('TOGGLE_TODO', (action, store) => store.toggle(action.id)),
)
handleAction({ type: 'ADD_TODO', text: 'Eat banana.' }, store) // -> "todo added"
handleAction({ type: 'TOGGLE_TODO', id: 0 }, store) // -> "todo toggled"
大小写值
案例值是两个论点的第一个论据 method 如果你只提供一种理由 method,将被视为默认情况).
大小写值可以是:
- 普通值
- 构造器/类
- 正则表达式
- 通配符( e)
_(中文(简体) ) - 上游函数
- 包含以上任何大小写的数组
您可以在一个多方法中混合所有大小写值类型 .
普通价值
如果大小写值既不是上位函数,正则表达式,构造符也不是通配符,则会使用深度严格的等同算法与调度函数的结果相匹配.
实例:
/**
* Function with case values as ordinary values.
* Values can be any arbitrary nested structure, or a primitive.
*
* Matched by the deep strict equal algorithm.
*
* @param {Object} player
* @returns {string} greeting
*/
const greet = multi(
method({ name: 'John', age: '30' }, 'Hello John!'),
method({ name: 'Jane', age: '25' }, 'Hi Jane!'),
method('Howdy stranger!'),
)
greet({ name: 'John', age: '30' }) // -> "Hello John!"
greet({ name: 'Jane', age: '25' }) // -> "Hi Jane!"
greet({ name: 'Jane', age: '40' }) // -> "Howdy stranger!"
构造器/类
如果大小写值是构建者,则与发送函数的结果相匹配的是严格的等值(===操作员,
如果失败—— instanceof 操作员.
示例
class Email {}
class SMS {}
/**
* Function with case values as constructors.
* Matched by strict equality check, followed by instanceof check.
*
* @param {Object} message
* @returns {string} status
*/
const sendMessage = multi(
method(Email, 'Sending email...'),
method(SMS, 'Sending SMS...'),
)
sendMessage(new Email()) // -> "Sending email..."
sendMessage(new SMS()) // -> "Sending SMS..."
sendMessage(Email) // -> "Sending email..."
sendMessage(SMS) // -> "Sending SMS..."
正则表达式
如果大小写值是正则表达式,则会与发送函数的结果相匹配,即严格等同(===操作员,
如果失败—— RegExp.prototype.test() 方法.
示例
/**
* Function with case values as regular expressions.
* Matched by RegExp.prototype.test() method
*
* @param {RegExp} pattern
* @returns {string} type
*/
const productCategory = multi(
method(/wine/, 'wine'),
method(/cheese/, 'cheese'),
method(/bread/, 'bread'),
)
productCategory('blue cheese') // -> "cheese"
productCategory('red wine') // -> "wine"
productCategory('white wine from Germany') // -> "wine"
productCategory('breadcrumbs') // -> "bread"
通关卡
如果大小写值是通配符(_它符合任何值.
当需要检查参数的正确性时,或者在(当多方法有多个有效签名时)绕来绕去时,Wildcards(尤其是与上游函数配对)非常有用.
Wildcard在功能上相当于总是返回真实的上方函数: () => true。它速度要快一点,因为它不会执行不必要的功能.
示例
const { multi, method, _ } = require('@arrows/multimethod')
/**
* Function with case values containing wildcards.
* These values always resolve to true.
*
* @param {any} a
* @param {any} b
* @param {any} c
* @returns {string}
*/
const checkArgs = multi(
(...args) => args.map((arg) => typeof arg),
// Skipping check on the first argument
method([_, 'function', 'function'], () => {
throw new Error('To many functions')
}),
// Skipping check on the second argument
method(['object', _, 'function'], () => {
throw new Error('Wrong combination')
}),
method((a, b, c) => 'ok'),
)
checkArgs(
1,
() => 2,
() => 3,
) // -> Error: To many functions
checkArgs({ id: 1 }, 2, () => 3) // -> Error: Wrong combination
checkArgs(1, { id: 2 }, () => 3) // -> "ok"
优先函数
如果大小写值是函数,则与发送函数的结果相匹配的是严格的等值(===操作员,
如果失败了 如果失败了 原始调度功能将被忽略,
,并且将使用所有提供的理由执行大小写值函数.
大小写值函数应返回布尔值(或至少输出将被视为布尔值).
如果返回值是真实的,那么我们有一个匹配.
示例
/**
* Function with case values as ordinary functions.
* Case value functions will be executed ignoring dispatch function values,
* and instead operating on raw arguments.
*
* It's like each method has its own dispatch function.
*
* It is useful when you can't express dispatch in one common function
* when each case has some specific rule.
*
* @param {Object} req
* @returns {string | Object} response value
*/
const router = multi(
method(
(req) => ['GET'].includes(req.method) && req.url === '/',
'Hello world!',
),
method(
(req) => ['GET', 'POST'].includes(req.method) && req.url === '/users',
[{ id: 1, name: 'John' }],
),
method('Oops!'),
)
router({ method: 'GET', url: '/' }) // -> "Hello world!"
router({ method: 'POST', url: '/' }) // -> "Oops!"
router({ method: 'GET', url: '/users' }) // -> [{ id: 1, name: "John" }]
在数组内预先指定函数
在一个数组内部的先入为主的函数有稍有不同的行为,它们作为参数收到调度函数在当前位置提供的值. 它们不会收到所有参数,也不会推翻调度功能.
这样你就可以微调每个争论的检查, 超越简单的平等检查.
示例
const { multi, method, _ } = require('@arrows/multimethod')
/* Custom predicates */
const not = (y) => (x) => x !== y
const notIn = (...args) => (x) => !args.includes(x)
/**
* Function with case value ans an array
* that contains predicate functions.
*
* Very powerful when paired with wildcards.
*
* @param {any} a
* @param {any} b
* @returns {string}
*/
const checkArgs2 = multi(
(a, b) => [typeof a, typeof b],
method([not('number'), _], () => {
throw new Error('First argument should be a number')
}),
method([_, notIn('string', 'number')], () => {
throw new Error('Second argument should be a number or a string')
}),
method((a, b) => 'ok'),
)
checkArgs2('a', 1) // -> Error: First argument should be a number
checkArgs2(1, [2, 3]) // -> Error: Second argument should be a number or a string
checkArgs2(1, 2) // -> "ok"
checkArgs2(5, 'b') // -> "ok"
有特殊情况的矩阵
如果大小写值是一个包含其中任一的数组:
- 构造器/类
- 正则表达式
- 通配符( e)
_(中文(简体) ) - 上游函数
然后,这些值将根据其特定的算法相匹配(但只有在它们处于数组的第一级时).
其它的都会使用深度严格的等同算法进行匹配.
这使得您可以使用最适合每个参数的参数发送多个参数.
示例
class Article {}
class Recipe {}
class PDF {}
class HTML {}
/**
* Function with case values as constructors wrapped in an array.
*
* It can be very useful, for example as a trivial alternative
* to otherwise complex visitor patter.
*
* @param {Object} document
* @param {Object} template
* @returns {string} embedding description
*/
const embed = multi(
method([Article, PDF], 'Embedding article inside PDF'),
method([Article, HTML], 'Embedding article inside HTML'),
method([Recipe, PDF], 'Embedding recipe inside PDF'),
method([Recipe, HTML], 'Embedding recipe inside HTML'),
)
embed(new Article(), new PDF()) // -> "Embedding article inside PDF"
embed(new Recipe(), new HTML()) // -> "Embedding recipe inside HTML"
你可能也注意到我已经在通配符和上游函数示例中使用了这种方法!
在一个多方法中不同类型大小写值的额外示例 :
const VIPs = ['[email protected]', '[email protected]']
/**
* Function with case values of different types.
*
* @param {Object} msg
* @returns {string | Object} response value
*/
const notify = multi(
(msg) => msg.type,
method(
(msg) => msg.type === 'email' && VIPs.includes(msg.email),
'Email from VIP!',
),
method('email', (msg) => `Email from ${msg.email}!`),
method('sms', (msg) => `SMS from ${msg.number}!`),
)
notify({ type: 'email', email: '[email protected]' }) // -> "Email from VIP!"
notify({ type: 'email', email: '[email protected]' }) // -> "Email from [email protected]!"
notify({ type: 'sms', number: '123456789' }) // -> "SMS from 123456789!"
对应值
相应的值是二分法的第二个参数 method (或者默认方法的第一个也是唯一的论据).
对应值可以是:
- 函数
- 任何其他价值
如果对应的值是一个函数,则当其大小写值匹配时,会用经过的参数执行.
如果对应的值不是函数,则当其大小写值匹配时会返回.
已经有许多例子,但还有另一个例子:
/**
* Classic recursive fibonacci.
* Do not use it for big numbers.
*
* As you can see, recursion works just like with ordinary functions.
*
* @param {number} n
* @returns {number} Nth element of fibonacci sequence
*/
const fib = multi(
method(0, 0),
method(1, 1),
method((n) => fib(n - 1) + fib(n - 2)),
)
fib(0) // -> 0
fib(1) // -> 1
fib(9) // -> 34
扩展多方法
多相方法是不可改变的,所以我们不能增加/克服方法,但是我们可以轻松地在现有的方法的基础上创建一个新的多相方法. 事实上,每次我们执行 method 函数,我们创建了新的多方法。 这种多式方法将拥有旧有的所有案例,加上新方法(你也可以通过使用同一种案例Value作为现有方法来"替换"一个方法).
以单一方法延伸
您可以通过执行创建新的多方法 method 函数,并传递一个碱基多方法作为第二个块的参数.
实例:
const baseAdd = multi(
(a, b) => [typeof a, typeof b],
method(['number', 'number'], (a, b) => a + b),
method(['string', 'string'], (a, b) => `${a}${b}`),
)
/**
* Creating a new multimethod with an additional method
*/
const add = method(['bigint', 'bigint'], (a, b) => a + b)(baseAdd)
add(1, 2) // -> 3
add('bat', 'man') // -> "batman"
add(1n, 2n) // -> 3n
const { multi, method } = require('@arrows/multimethod')
/**
* @param {string} language
* @returns ${string} greeting
*/
const baseGreet = multi(
method('ru', 'Привет!'),
method('es', '¡Hola!'),
method('pl', 'Cześć!'),
)
/**
* Creating a new multimethod with added default method
*/
const greet = method('Hello!')(baseGreet)
greet('ru') // -> "Привет!"
greet('es') // -> "¡Hola!"
greet('pl') // -> "Cześć!"
greet('fr') // -> "Hello!"
以多种方法扩展
您可以用通用方法创建多种新方法 compose 函数,或使用内置 fromMulti 函数,可以给您额外的类型检查和更好的错误处理.
实例:
const baseHandleHTTPError = multi(
method(400, 'Incorrect request.'),
method(404, 'The path does not exist.'),
)
/**
* Creating a new multimethod with many new methods via generic functional `compose`
*/
const handleHTTPError = compose(
method(403, 'You do not have access to this resource.'),
method(418, 'We are all teapots!'),
)(baseHandleHTTPError)
handleHTTPError(400) // -> "Incorrect request."
handleHTTPError(418) // -> "We are all teapots!"
const baseArea = multi(
(shape) => shape.type,
method('rectangle', (shape) => shape.a * shape.b),
method('square', (shape) => shape.a ** 2),
)
/**
* Creating a new multimethod with many new methods via `fromMulti` function
*/
const area = fromMulti(
method('circle', (shape) => Math.PI * shape.r ** 2),
method('triangle', (shape) => 0.5 * shape.a * shape.h),
)(baseArea)
area({ type: 'square', a: 5 }) // -> 25
area({ type: 'circle', r: 3 }) // -> 28.274333882308138
方法优先权
当你执行 method 函数,该大小写将添加到多方法的前端。 如果部分适用 method 转到 multi 财务报告和审定财务报表 fromMulti 函数,它们将被从下到上添加到上 - 这样它们就能在最后的多方法中保持顺序 .
多方法内方法的顺序决定其优先性.
实例:
/** Priorities for methods inside `evenMoreExtended` function */
const base = multi(
method('a', 'priority: 5'),
method('b', 'priority: 6'),
method('c', 'priority: 7'),
)
const extended = method('d', 'priority: 4')(base)
const evenMoreExtended = fromMulti(
method('e', 'priority: 1'),
method('f', 'priority: 2'),
method('g', 'priority: 3'),
)(extended)
/**
* Function where the priority does matter
*
* @param {number} points
* @returns {number} grade
*/
const baseGradeExam = multi(
method((points) => points < 10, 'failed'),
method((points) => points <= 15, 'ok'),
method((points) => points > 15, 'good'),
)
const gradeExam = fromMulti(
method((points) => points === 0, 'terrible'),
method((points) => points > 20, 'excellent'),
)(baseGradeExam)
gradeExam(0) // -> 'terrible'
gradeExam(5) // -> 'failed'
gradeExam(10) // -> 'ok'
gradeExam(15) // -> 'ok'
gradeExam(20) // -> 'good'
gradeExam(25) // -> 'excellent'
咖喱鱼
多方法支持以调度功能为基础的自動咖喱和手动咖喱/克通克.
自动咖喱
多种方法与所有通用 curry 函数。 currying 是基于调度函数的长度,所以您应该提供明确的调度(默认调度是一个variadic函数,所以它的长度是 0) (中文(简体) ).
示例
import curry from '@arrows/composition/curry'
import { multi, method } from '@arrows/multimethod'
/**
* Currying with a generic `curry` function.
* Explicit dispatch function (length: 2).
*
* Note that you can still use non-functions as corresponding values.
*
* @param {string} type
* @param {string} source
* @returns {string} info
*/
const play = multi(
(type, source) => type,
method('audio', (type, source) => `Playing audio from: ${source}`),
method('video', (type, source) => `Playing video from: ${source}`),
)
const curriedPlay = curry(play)
const playAudio = curriedPlay('audio')
const playVideo = curriedPlay('video')
playAudio('songs.io/123') // -> "Playing audio from: songs.io/123"
playVideo('movies.com/123') // -> "Playing video from: movies.com/123"
手动咖喱
如果你更喜欢手动咖喱,或者已经拥有使用手动咖喱并想用来作为方法的功能,那么用多方法就很容易做到. 就像自动咖喱一样,执行是基于调度功能.
实例:
const mapArray = (fn) => (arr) => arr.map(fn)
const mapString = (fn) => (str) => [...str].map(fn)
/**
* Manually curried function - each chunk has one argument.
*/
const map = multi(
(fn) => (val) => (Array.isArray(val) ? 'array' : typeof val),
method('array', mapArray),
method('string', mapString),
)
map((char) => char.charCodeAt(0))('Hello') // -> [72, 101, 108, 108, 111]
map((item) => item * 2)([1, 2, 3]) // -> [2, 4, 6]
/**
* Manually chunked function - chunks can have more than one argument,
* they can also have default values.
*
* Note that you can still use non-functions as corresponding values.
*/
const checkGrammar = multi(
(config = {}) => (text, language) => language,
method('en', 'Checking English grammar'),
method('pl', 'Checking Polish grammar'),
)
const checkTypos = checkGrammar({ typos: true })
checkTypos('mistkae', 'en') // -> "Checking English grammar"
checkTypos('błąt', 'pl') // -> "Checking Polish grammar"
注:为了能够做到这一点 multi 函数在创建多方法时计数块(块),在返回的值不是函数或出错之前执行不设参数的调度函数。 这有一个限制 您不应该使用任何基于参数的计算作为默认值。如果你这样做,图书馆将无法正确计算段数.
例如,这不会如意:
/**
* This won't work, because of the default value calculation
* based on the `a` argument.
*/
const fn = multi(
(a) => (b = a.foo) => a,
method(/* some case */),
method(/* some case */),
)
注:如果在使用手动曲折时使用自定义的大小写Value函数,这些小写Value函数的参数会自动被平分.
例如:
/**
* Even though we don't use dispatch value, we have to add
* a dispatch function to inform the multimethod of how to handle this.
*
* As you can see, you don't have to curry a caseValue function.
*/
const fn = multi(
a => b => c => null,
method((a, b, c) => /* some check */, a => b => c => /* some return */),
method((a, b, c) => /* some check */, a => b => c => /* some return */),
)
定义类型
如果您使用 TypeScript, 您可能想要使您的多方法类型安全 。 虽然多方法的动态性质使得编译器很难保证多方法的正确组合,但您仍然可以通过定义其界面来确保多方法的正确使用.
TypeScript支持通过接口(或类型别名等同)和相交方式定义的函数超载.
通过接口添加类型
从零开始创建新的多方法 :
import { method, multi, Multi } from '@arrows/multimethod'
/**
* Multimethod type defined using interface.
*
* Extending `Multi` type guarantees that the multimethod will
* be recognized correctly when extended by `fromMulti` function.
*/
interface Add extends Multi {
(x: number, y: number): number
(x: string, y: string): string
}
const add: Add = multi(
<T>(x: T, x: T): T => [x, y],
method([Number, Number], (x: number, y: number): number => x + y),
method([String, String], (x: string, y: string): string => `${x}${y}`),
)
/*
add(3, 3) // -> 6
add('foo', 'bar') // -> "foobar"
add(5, [1, 2]) // compiler error
*/
export { Add }
export default add
从现有方法创建新的多方法:
import { fromMulti, method } from '@arrows/multimethod'
import baseAdd, { Add as BaseAdd } from './add'
/**
* Multimethod type defined by extending existing interface.
*/
interface Add extends BaseAdd {
(x: bigint, y: bigint): bigint
}
const add: Add = fromMulti(
method([BigInt, BigInt], (x: bigint, y: bigint) => x + y),
)(baseAdd)
add(3, 3) // -> 6
add('foo', 'bar') // -> "foobar"
add(3n, 5n) // -> 8n
add(3, [1, 2, 3]) // compiler error
通过类型别名和相交相添加类型
从零开始创建新的多方法 :
import { method, multi, Multi } from '@arrows/multimethod'
/**
* Multimethod type defined using interface.
*
* Using intersection with `Multi` type guarantees that the multimethod
* will be recognized correctly when extended by `fromMulti` function.
*/
type Add = Multi & {
(x: number, y: number): number
(x: string, y: string): string
}
const add: Add = multi(
<T>(x: T, x: T): T => [x, y],
method([Number, Number], (x: number, y: number): number => x + y),
method([String, String], (x: string, y: string): string => `${x}${y}`),
)
/*
add(3, 3) // -> 6
add('foo', 'bar') // -> "foobar"
add(5, [1, 2]) // compiler error
*/
export { Add }
export default add
从现有方法创建新的多方法:
import { fromMulti, method } from '@arrows/multimethod'
import baseAdd, { Add as BaseAdd } from './add'
/**
* Multimethod type defined by intersection with existing type.
*/
type Add = BaseAdd & {
(x: bigint, y: bigint): bigint
}
const add: Add = fromMulti(
method([BigInt, BigInt], (x: bigint, y: bigint) => x + y),
)(baseAdd)
add(3, 3) // -> 6
add('foo', 'bar') // -> "foobar"
add(3n, 5n) // -> 8n
add(3, [1, 2, 3]) // compiler error
使用现有类型添加类型
通常,创建多方法的最好方法是首先定义组件函数及其类型别名——这样你就可以使用多方法或原始函数——按照什么最合适. 这样也可以重新使用多方法类型特定部件的型号别名.
从零开始创建新的多方法 :
import { method, multi, Multi } from '@arrows/multimethod'
/**
* Component functions with type aliases.
*/
type AddNumbers = (x: number, y: number) => number
const addNumbers: AddNumbers = (x, y) => x + y
type AddStrings = (x: string, y: string) => string
const addStrings: AddStrings = (x, y) => `${x}${y}`
/**
* Multimethod type defined by intersection of existing types.
*
* Using intersection with `Multi` type guarantees that the multimethod
* will be recognized correctly when extended by `fromMulti` function.
*/
type Add = Multi & AddNumbers & AddStrings
const add: Add = multi(
<T>(x: T, y: T): T => [x, y],
method([Number, Number], addNumbers),
method([String, String], addStrings),
)
/*
add(3, 3) // -> 6
add('foo', 'bar') // -> "foobar"
add(3, [1, 2, 3]) // compiler error
*/
export { addNumbers, addStrings, Add }
export default add
从现有方法创建新的多方法:
import { fromMulti, method } from '@arrows/multimethod'
import baseAdd, { Add as BaseAdd } from './add'
type AddBigInts = (x: bigint, y: bigint) => bigint
const addBigInts: AddBigInts = (x, y) => x + y
/**
* Multimethod type defined by intersection of existing types.
*/
type Add = BaseAdd & AddBigInts
// prettier-ignore
const add: Add = fromMulti(
method([BigInt, BigInt], addBigInts),
)(baseAdd)
add(3, 3) // -> 6
add('foo', 'bar') // -> "foobar"
add(3n, 5n) // -> 8n
add(3, [1, 2, 3]) // compiler error
API 参考
多个
创建多方法 - 一个能够动态选择正确执行的函数, 依据是任意发出其论点.
参数
first- 第一个论据可以是发送函数,也可以是部分应用的方法methods- 部分应用方法的任意数量(可选)
回返
- 返回可用作普通函数的不可改变多方法
接口
(dispatch | method, method?, method?, ..., method?) => multimethod
实例
创建自定义发送和无方法的多方法 :
const fn = multi((x) => typeof x)
fn('foo') // -> throws an Error (because of no matching methods), but is useful as a base for extensions
创建带有默认调度和一些方法的多方法 :
const makeSound = multi(
method('cat', () => 'Meow!'),
method('dog', () => 'Woof!'),
method(() => 'Hello!'), // default method (ony one argument provided)
)
makeSound('cat') // -> 'Meow!'
makeSound('dog') // -> 'Woof!'
makeSound('cow') // -> 'Hello!' (that's a rather unusual cow)
创建自定义调度和一些方法的多方法 :
const multiply = multi(
(multiplier, x) => [typeof multiplier, typeof x],
method(['number', 'number'], (multiplier, x) => x * multiplier),
method(['number', 'string'], (multiplier, x) => x.repeat(multiplier)),
)
multiply(2, 5) // -> 10
multiply(3, 'Beetlejuice! ') // -> 'Beetlejuice! Beetlejuice! Beetlejuice! ' (do not read it out loud)
multiply(2, [1, 2, 3]) // -> throws an Error (no match and no default method for these arguments)
方法
将方法添加到多方法
参数
-
caseValue- 调度函数结果的相配值- 如果函数,则使用输入参数执行(即使发送被块化,也总是被卸载,
- 如果是构造器,则首先以引用值相匹配,如果失败,则以操作者为例.
-
correspondingValue- 该函数在匹配时应当返回的值- 如果函数随输入参数而执行(根据发送函数而切入或未切入)
-
multimethod- 新的多方法基础的多方法
回返
- 新的多方法( 基准方法不变 )
接口
(caseValue?, correspondingValue) => (multimethod) => new_multimethod
实例
作为函数的默认方法 :
const sayHello = multi((user) => user.lang)
const sayHelloWithDefault = method((user) => `Hello ${user.name}!`)(sayHello)
sayHelloWithDefault({ name: 'Alejandro', lang: 'es' }) // -> 'Hello Alejandro!'
默认方法作为其它值 :
const sayHello = multi((user) => user.lang)
const sayHelloWithDefault = method('Hello!')(sayHello)
sayHelloWithDefault({ name: 'Alejandro', lang: 'es' }) // -> 'Hello!'
以大小写值为普通值和以相应值为函数的方法:
const add = multi((a, b) => [typeof a, typeof b])
const extendedAdd = method(['number', 'number'], (a, b) => a + b)(add)
extendedAdd(1, 2) // -> 3
将大小写值和相应的值作为普通值的方法:
const getHexColor = multi((color) => color)
const extendedGetHexColor = method('red', '#FF0000')(getHexColor)
extendedGetHexColor('red') // -> '#FF0000'
以大小写值为函数、以相应值为普通值的方法:
class Enemy {}
const is = (prototype) => (value) => value instanceof prototype
const greet = multi((person) => person)
// Matches, when case function executed with initial arguments returns truthy value
const extendedGreet = method(is(Enemy), 'Goodbye!')(greet)
extendedGreet(new Enemy()) // -> 'Goodbye!'
以大小写值和相应值为功能的方法:
class Car {
drive() {
return 'driving...'
}
}
class Human {
walk() {
return 'walking...'
}
}
const is = (prototype) => (value) => value instanceof prototype
const go = multi(
method(is(Car), (entity) => entity.drive()), // lets add one case to original multimethod
)
const extendedGo = method(is(Human), (entity) => entity.walk())(go)
extendedGo(new Car()) // -> 'driving...'
extendedGo(new Human()) // -> 'walking...'
将大小写作为构建者的方法:
class Email {}
class SMS {}
const send = multi(
method(Email, 'Sending email...'), // lets add one case to the original multimethod
)
const extendedSend = method(SMS, 'Sending SMS...')(send)
extendedSend(new Email()) // -> 'Sending email...'
extendedSend(new SMS()) // -> 'Sending SMS...'
从多
从现有方法创建新的多方法,方便添加多方法.
参数
methods- 部分应用方法的任意数量multimethod- 您想要基于的多方法
回返
- 新的多方法( 基本方法不变 )
接口
(method, method?, ..., method?) => (multimethod) => new_multimethod
示例
以现有方法为基础创建新的多方法:
const add = multi(
(a, b) => [typeof a, typeof b],
method(['number', 'number'], (a, b) => a + b),
method(['string', 'string'], (a, b) => `${a}${b}`),
)
const extendedAdd = fromMulti(
method(['bigint', 'bigint'], (a, b) => a + b),
method(['number', 'bigint'], (a, b) => BigInt(a) + b),
method(['bigint', 'number'], (a, b) => a + BigInt(b)),
)(add)
extendedAdd(1, 2) // -> 3
extendedAdd('foo', 'bar') // -> 'foobar'
extendedAdd(2n, 3n) // -> 5n
extendedAdd(5, 5n) // -> 10n
extendedAdd(9n, 2) // -> 11n
检查
用多方法的构件获取对象.
注意: 您不应该改变其任何值 .
参数
multimethod- 要检查的多方法
回返
- 带有下列精度的检查对象:
dispatch- 显示调度功能entries- 显示所有[caseValue, correspondingValue]条目cases- 显示所有caseValue项目values- 显示所有correspondingValue项目
接口
(multimethod) => inspection_object
示例
const { multi, method, inspect, _ } = require('@arrows/multimethod')
const mixColors = multi(
method(['yellow', 'red'], 'orange'),
method(['black', _], 'black'),
method(['red', 'blue'], 'purple'),
method('no idea'),
)
const inspectionObject = inspect(mixColors)
console.dir(inspectionObject.entries, { depth: null })
/* Result:
[
[ { type: 'data', value: [ 'yellow', 'red' ] }, 'orange' ],
[
{
type: 'mixed',
values: [ { type: 'data', value: 'black' }, { type: 'wildcard' } ]
},
'black'
],
[ { type: 'data', value: [ 'red', 'blue' ] }, 'purple' ],
[ { type: 'default' }, 'no idea' ]
]
*/
console.dir(inspectionObject.dispatch, { depth: null })
/* Result:
[Function: implicitDispatch]
*/
通配符
符合任何值的特殊占位符 .
示例
const { multi, method, _ } = require('@arrows/multimethod')
const hasCallback = multi(
(a, b) => [typeof a, typeof b],
method(['function', __], true),
method([__, 'function'], true),
method(false),
)
hasCallback(() => {}, 'a') // -> true
hasCallback('b', () => {}) // -> true
hasCallback(
() => {},
() => {},
) // -> true
hasCallback(3, 4) // -> false
文章
- 通过多方法消除物体的多态性 叶霍纳坦·沙维特 链接
许可证
项目处于开放式、非限制性 ISC 许可证。 。 。 .