@arrows/multimethod

中文文档
2.1.0发布于 2021/10/15ISCCommonJS近 30 天下载 23.6万

功能多方法库

中文使用文档
文档目录20 节
  1. 目录
  2. 导言
  3. 安装
  4. 使用量
  5. 快速实例
  6. 多方法解剖
  7. 扩展多方法
  8. 方法优先权
  9. 咖喱鱼
  10. 定义类型
  11. 通过接口添加类型
  12. 通过类型别名和相交相添加类型
  13. API 参考
  14. 多个
  15. 方法
  16. 从多
  17. 检查
  18. 通配符
  19. 文章
  20. 许可证

arrows - multimethod

@ rows/ 多方法

npm (scoped) CircleCI Codecov npm bundle size (scoped) GitHub

目录

  1. 导言
  2. 安装
  3. 使用量
  4. API 参考
  5. 文章
  6. 许可证

导言

多方法是具有超能的功能——它们可以做所有普通的功能所能做的,但另外还有:

  • 可在提供的论点基础上选择适当的执行,而不明确附加条件的逻辑,
  • 很容易扩展,不需要修改原始代码,
  • 允许您写出干净、简洁和解码.

多方法库提供了一套微小的更高级的功能来创建强大的,不可变的多方法——在功能上.

图书馆有 内置类型定义,提供了极佳的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 (或者默认方法的第一个也是唯一的论据).

对应值可以是:

  1. 函数
  2. 任何其他价值

如果对应的值是一个函数,则当其大小写值匹配时,会用经过的参数执行.

如果对应的值不是函数,则当其大小写值匹配时会返回.

已经有许多例子,但还有另一个例子:

/**
 * 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 许可证。 。 。 .

版本与发布记录

@arrows/multimethod 共发布 40 个版本,首次发布于 2019/09/17;过去 12 个月发布了 0 个版本。

版本发布日期距今
2.1.0latest2021/10/155 年前
2.0.02021/10/155 年前
1.4.12021/10/145 年前
1.4.02021/10/145 年前
1.3.02021/10/135 年前
1.2.12021/10/135 年前

关于 @arrows/multimethod 的常见问题

@arrows/multimethod 是什么?

功能多方法库

如何安装 @arrows/multimethod?

在项目目录运行 npm install @arrows/multimethod,或使用 pnpm add / yarn add / bun add。

@arrows/multimethod 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/arrows__multimethod 是否存在。

@arrows/multimethod 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@arrows/multimethod 可以商用吗?

@arrows/multimethod 使用 ISC 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@arrows/multimethod 的下载量有多少?

根据 npm 官方下载统计,@arrows/multimethod 在 2026/10/07 – 2026/11/05 这 30 天内被下载 236,458 次,日均约 7,882 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@arrows/multimethod 还在维护吗?

最新版本 2.1.0 发布于 2021/10/15(5 年前);至今共发布 40 个版本,其中过去 12 个月发布了 0 个;首次发布于 2019/09/17。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。