Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

protoduck

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

protoduck - npm Package Compare versions

Comparing version 3.3.2 to 4.0.0

CHANGELOG.md

447

index.js
'use strict'
var genfun = require('genfun')
const genfun = require('genfun')
var Protocol = module.exports = function (types, spec, opts) {
if (!isArray(types)) {
// protocol(spec, opts?) syntax for method-based protocols
opts = spec
spec = types
types = []
class Duck extends Function {
// Duck.impl(Foo, [String, Array], { frob (str, arr) { ... }})
impl (target, types, impls) {
if (!impls && !isArray(types)) {
impls = types
types = []
}
if (!impls && this.isDerivable) {
impls = this._defaultImpls
}
if (!impls) {
impls = {}
}
if (typeof target === 'function' && !target.isGenfun) {
target = target.prototype
}
checkImpls(this, target, impls)
checkArgTypes(this, types)
this._constraints.forEach(c => {
if (!c.verify(target, types)) {
throw new Error(`Implementations of ${
this.name || 'this protocol'
} must first implement ${
c.parent.name || 'its constraint protocols defined in opts.where.'
}`)
}
})
this._methodNames.forEach(name => {
defineMethod(this, name, target, types, impls)
})
}
var proto = function (target, types, impls) {
return Protocol.impl(proto, target, types, impls)
}
proto._metaobject = opts && opts.metaobject
proto._types = types
proto._defaultImpls = {}
proto._gfTypes = {}
proto._derivable = true
proto._methodNames = Object.keys(spec)
proto._methodNames.forEach(function (name) {
proto[name] = proto._metaobject
? Protocol.meta.createGenfun(proto._metaobject, proto, null, name)
: _metaCreateGenfun(null, proto, null, name)
var gfTypes = spec[name]
// genfun specs can have a fn attached to the end as a default impl
if (typeof gfTypes[gfTypes.length - 1] === 'function') {
proto._defaultImpls[name] = gfTypes.pop()
} else {
proto._derivable = false
hasImpl (arg, args) {
args = args || []
const fns = this._methodNames
var gf
if (typeof arg === 'function' && !arg.isGenfun) {
arg = arg.prototype
}
proto._gfTypes[name] = gfTypes.map(function (typeId) {
var idx = proto._types.indexOf(typeId)
if (idx === -1) {
throw new Error('type `' + typeId + '` for function `' + name +
'` does not match any protocol types')
args = args.map(arg => {
if (typeof arg === 'function' && !arg.isGenfun) {
return arg.prototype
} else {
return idx
return arg
}
})
})
return proto
for (var i = 0; i < fns.length; i++) {
gf = arg[fns[i]]
if (!gf ||
(gf.hasMethod
? !gf.hasMethod.apply(gf, args)
: typeof gf === 'function')) {
return false
}
}
return true
}
// MyDuck.matches('a', ['this', 'c'])
matches (thisType, argTypes) {
if (!argTypes && isArray(thisType)) {
argTypes = thisType
thisType = 'this'
}
if (!thisType) {
thisType = 'this'
}
if (!argTypes) {
argTypes = []
}
return new Constraint(this, thisType, argTypes)
}
}
Duck.prototype.isDuck = true
Duck.prototype.isProtocol = true
Protocol.noImplFound = genfun.noApplicableMethod
const Protoduck = module.exports = define(['duck'], {
createGenfun: ['duck', _metaCreateGenfun],
addMethod: ['duck', _metaAddMethod]
}, {name: 'Protoduck'})
function typeName (obj) {
return (/\[object ([a-zA-Z0-9]+)\]/)
.exec(({}).toString.call(obj))[1]
}
const noImplFound = module.exports.noImplFound = genfun.noApplicableMethod
function installMethodErrorMessage (proto, gf, target, name) {
Protocol.noImplFound.add([gf], function (gf, thisArg, args) {
var msg =
'No ' + (proto.name || 'protocol') + ' impl for `' +
(target ? typeName(thisArg) + '#' : '') +
name +
'(' +
[].map.call(args, typeName).join(', ') + ')`'
msg += '\n\n'
msg += 'You must implement '
msg += (proto.name || 'the protocol `' + name + '` belongs to')
msg += ' in order to call `' + name + '` with these arguments.\n'
var err = new Error(msg)
err.protocol = proto
err.function = gf
err.thisArg = thisArg
err.args = args
throw err
module.exports.define = define
function define (types, spec, opts) {
if (!isArray(types)) {
// protocol(spec, opts?) syntax for method-based protocols
opts = spec
spec = types
types = []
}
const duck = function (thisType, argTypes) {
return duck.matches(thisType, argTypes)
}
Object.setPrototypeOf(duck, Duck.prototype)
duck.isDerivable = true
Object.defineProperty(duck, 'name', {
value: (opts && opts.name) || 'Protocol'
})
if (opts && opts.where) {
let where = opts.where
if (!isArray(opts.where)) { where = [opts.where] }
duck._constraints = where.map(w => w.isProtocol // `where: [Foo]`
? w.matches()
: w
)
} else {
duck._constraints = []
}
duck.isProtocol = true
duck._metaobject = opts && opts.metaobject
duck._types = types
duck._defaultImpls = {}
duck._gfTypes = {}
duck._methodNames = Object.keys(spec)
duck._methodNames.forEach(name => {
checkMethodSpec(duck, name, spec)
})
duck._constraints.forEach(c => c.attach(duck))
return duck
}
Protocol.isDerivable = function (proto) { return proto._derivable }
Protocol.hasImpl = function (proto, arg, args) {
args = args || []
if (isArray(arg)) {
args = arg
arg = null
function checkMethodSpec (duck, name, spec) {
let gfTypes = spec[name]
if (typeof gfTypes === 'function') {
duck._defaultImpls[name] = gfTypes
gfTypes = [gfTypes]
} if (typeof gfTypes[gfTypes.length - 1] === 'function') {
duck._defaultImpls[name] = gfTypes.pop()
} else {
duck.isDerivable = false
}
var fns = proto._methodNames
var gf
for (var i = 0; i < fns.length; i++) {
if (arg) {
gf = arg[fns[i]]
duck._gfTypes[name] = gfTypes.map(typeId => {
const idx = duck._types.indexOf(typeId)
if (idx === -1) {
throw new Error(
`type '${
typeId
}' for function '${
name
}' does not match any protocol types (${
duck._types.join(', ')
}).`
)
} else {
gf = proto[fns[i]]
return idx
}
if (!gf ||
(gf.hasMethod
? !gf.hasMethod.apply(gf, args)
: typeof gf === 'function')) {
return false
}
}
return true
})
}
Protocol.impl = function (proto, target, types, implementations) {
if (isArray(target)) {
// Proto([Array], { map() { ... } })
implementations = types
types = target
target = null
} else if (types && !isArray(types)) {
// Proto(Array, { map() { ... } })
implementations = types
types = []
function defineMethod (duck, name, target, types, impls) {
const methodTypes = duck._gfTypes[name].map(function (typeIdx) {
return types[typeIdx]
})
for (let i = methodTypes.length - 1; i >= 0; i--) {
if (methodTypes[i] === undefined) {
methodTypes.pop()
} else {
break
}
}
if (typeof target === 'function') {
target = target.prototype
const useMetaobject = duck._metaobject && duck._metaobject !== Protoduck
// `target` does not necessarily inherit from `Object`
if (!Object.prototype.hasOwnProperty.call(target, name)) {
// Make a genfun if there's nothing there
const gf = useMetaobject
? duck._metaobject.createGenfun(duck, target, name, null)
: _metaCreateGenfun(duck, target, name, null)
target[name] = gf
} else if (typeof target[name] === 'function' && !target[name].isGenfun) {
// Turn non-gf functions into genfuns
const gf = useMetaobject
? duck._metaobject.createGenfun(duck, target, name, target[name])
: _metaCreateGenfun(duck, target, name, target[name])
target[name] = gf
}
if (!implementations && proto._derivable) {
implementations = proto._defaultImpls
const fn = impls[name] || duck._defaultImpls[name]
if (fn) { // checkImpls made sure this is safe
useMetaobject
? duck._metaobject.addMethod(duck, target, name, methodTypes, fn)
: _metaAddMethod(duck, target, name, methodTypes, fn)
}
Object.keys(proto).forEach(function (name) {
if (name[0] !== '_' &&
!implementations[name] &&
!proto._defaultImpls[name]) {
throw new Error('missing implementation for `' + name + '`')
}
function checkImpls (duck, target, impls) {
duck._methodNames.forEach(function (name) {
if (
!impls[name] &&
!duck._defaultImpls[name] &&
// Existing methods on the target are acceptable defaults.
typeof target[name] !== 'function'
) {
throw new Error(`Missing implementation for ${
formatMethod(duck, name, duck.name)
}. Make sure the method is present in your ${
duck.name || 'protocol'
} definition. Required methods: ${
duck._methodNames.filter(m => {
return !duck._defaultImpls[m]
}).map(m => formatMethod(duck, m)).join(', ')
}.`)
}
})
var pTypes = proto._types
if (types.length > pTypes.length) {
throw new Error('protocol expects to be defined across at least ' +
pTypes.length + ' types, but ' + types.length +
' were specified.')
} else if (types.length < pTypes.length) {
for (var i = 0; i < pTypes.length - types.length; i++) {
types.push(Object)
Object.keys(impls).forEach(function (name) {
if (duck._methodNames.indexOf(name) === -1) {
throw new Error(
`${name}() was included in the impl, but is not part of ${
duck.name || 'the protocol'
}. Allowed methods: ${
duck._methodNames.map(m => formatMethod(duck, m)).join(', ')
}.`
)
}
})
}
function formatMethod (duck, name, withDuckName) {
return `${
withDuckName && duck.name ? `${duck.name}#` : ''
}${name}(${duck._gfTypes[name].map(n => duck._types[n]).join(', ')})`
}
function checkArgTypes (duck, types) {
var requiredTypes = duck._types
if (types.length > requiredTypes.length) {
throw new Error(
`${
duck.name || 'Protocol'
} expects to be defined across ${
requiredTypes.length
} type${requiredTypes.length > 1 ? 's' : ''}, but ${
types.length
} ${types.length > 1 ? 'were' : 'was'} specified.`
)
}
Object.keys(implementations).forEach(function (name) {
if (proto._methodNames.indexOf(name) === -1) {
throw new Error('`' + name + '` is not part of the protocol')
}
function typeName (obj) {
return (/\[object ([a-zA-Z0-9]+)\]/).exec(({}).toString.call(obj))[1]
}
function installMethodErrorMessage (proto, gf, target, name) {
noImplFound.add([gf], function (gf, thisArg, args) {
let parent = Object.getPrototypeOf(thisArg)
while (parent && parent[name] === gf) {
parent = Object.getPrototypeOf(parent)
}
})
proto._methodNames.forEach(function (name) {
var fn = implementations[name] || proto._defaultImpls[name]
var methodTypes = calculateMethodTypes(name, proto, types)
if (target != null && !{}.hasOwnProperty.call(target, name)) {
target[name] = proto._metaobject
? Protocol.meta.createGenfun(proto._metaobject, proto, target, name)
: _metaCreateGenfun(null, proto, target, name)
if (parent && parent[name] && typeof parent[name] === 'function') {
}
proto._metaobject
? Protocol.meta.addMethod(proto._metaobject, proto, target, name, methodTypes, fn)
: _metaAddMethod(null, proto, target, name, methodTypes, fn)
var msg = `No ${typeName(thisArg)} impl for ${
proto.name ? `${proto.name}#` : ''
}${name}(${[].map.call(args, typeName).join(', ')}). You must implement ${
proto.name
? formatMethod(proto, name, true)
: `the protocol ${formatMethod(proto, name)} belongs to`
} in order to call ${typeName(thisArg)}#${name}(${
[].map.call(args, typeName).join(', ')
}).`
const err = new Error(msg)
err.protocol = proto
err.function = gf
err.thisArg = thisArg
err.args = args
err.code = 'ENOIMPL'
throw err
})
}
function calculateMethodTypes (name, proto, types) {
return proto._gfTypes[name].map(function (typeIdx) {
return types[typeIdx]
})
function isArray (x) {
return Object.prototype.toString.call(x) === '[object Array]'
}
// MOP
function _metaCreateGenfun (_mo, proto, target, name) {
var gf = genfun()
// Metaobject Protocol
Protoduck.impl(Protoduck) // defaults configured by definition
function _metaCreateGenfun (proto, target, name, deflt) {
var gf = genfun({
default: deflt,
name: `${proto.name ? `${proto.name}#` : ''}${name}`
})
installMethodErrorMessage(proto, gf, target, name)
gf.protocol = proto
gf.duck = proto
return gf
}
function _metaAddMethod (_mo, proto, target, name, methodTypes, fn) {
return (target || proto)[name].add(methodTypes, fn)
function _metaAddMethod (duck, target, name, methodTypes, fn) {
return target[name].add(methodTypes, fn)
}
Protocol.meta = Protocol(['a'], {
createGenfun: ['a'],
addMethod: ['a']
})
// Constraints
class Constraint {
constructor (parent, thisType, argTypes) {
this.parent = parent
this.target = thisType
this.types = argTypes
}
Protocol.meta([], {
createGenfun: _metaCreateGenfun,
addMethod: _metaAddMethod
})
attach (obj) {
this.child = obj
if (this.target === 'this') {
this.thisIdx = 'this'
} else {
const idx = this.child._types.indexOf(this.target)
if (idx === -1) {
this.thisIdx = null
} else {
this.thisIdx = idx
}
}
this.indices = this.types.map(typeId => {
if (typeId === 'this') {
return 'this'
} else {
const idx = this.child._types.indexOf(typeId)
if (idx === -1) {
return null
} else {
return idx
}
}
})
}
function isArray (x) {
return Object.prototype.toString.call(x) === '[object Array]'
verify (target, types) {
const thisType = (
this.thisIdx === 'this' || this.thisIdx == null
)
? target
: types[this.thisIdx]
const parentTypes = this.indices.map(idx => {
if (idx === 'this') {
return target
} else if (idx === 'this') {
return types[this.thisIdx]
} else if (idx === null) {
return Object
} else {
return types[idx] || Object.prototype
}
})
return this.parent.hasImpl(thisType, parentTypes)
}
}
Constraint.prototype.isConstraint = true
{
"name": "protoduck",
"version": "3.3.2",
"version": "4.0.0",
"description": "Fancy duck typing for the most serious of ducks.",

@@ -10,6 +10,9 @@ "main": "index.js",

"scripts": {
"preversion": "npm t",
"postversion": "npm publish && git push --follow-tags",
"prerelease": "npm t",
"postrelease": "npm publish && git push --follow-tags",
"pretest": "standard",
"test": "nyc -- mocha --reporter spec"
"release": "standard-version -s",
"test": "tap -J --coverage test/*.js",
"update-coc": "weallbehave -o . && git add CODE_OF_CONDUCT.md && git commit -m 'docs(coc): updated CODE_OF_CONDUCT.md'",
"update-contrib": "weallcontribute -o . && git add CONTRIBUTING.md && git commit -m 'docs(contributing): updated CONTRIBUTING.md'"
},

@@ -30,2 +33,4 @@ "repository": {

"clojure",
"haskell",
"rust",
"generic",

@@ -46,9 +51,13 @@ "functions",

"dependencies": {
"genfun": "^3.2.1"
"genfun": "^4.0.1"
},
"devDependencies": {
"mocha": "^3.0.2",
"nyc": "^8.1.0",
"standard": "^8.0.0"
"mocha": "^3.2.0",
"nyc": "^10.2.0",
"standard": "^10.0.2",
"standard-version": "^4.0.0",
"tap": "^10.3.2",
"weallbehave": "^1.0.3",
"weallcontribute": "^1.0.8"
}
}

@@ -1,7 +0,7 @@

# Protoduck [![Travis](https://img.shields.io/travis/zkat/protoduck.svg)](https://travis-ci.org/zkat/protoduck) [![npm version](https://img.shields.io/npm/v/protoduck.svg)](https://npm.im/protoduck) [![license](https://img.shields.io/npm/l/protoduck.svg)](https://npm.im/protoduck)
# protoduck [![npm version](https://img.shields.io/npm/v/protoduck.svg)](https://npm.im/protoduck) [![license](https://img.shields.io/npm/l/protoduck.svg)](https://npm.im/protoduck) [![Travis](https://img.shields.io/travis/zkat/protoduck.svg)](https://travis-ci.org/zkat/protoduck) [![AppVeyor](https://ci.appveyor.com/api/projects/status/github/zkat/protoduck?svg=true)](https://ci.appveyor.com/project/zkat/protoduck) [![Coverage Status](https://coveralls.io/repos/github/zkat/protoduck/badge.svg?branch=latest)](https://coveralls.io/github/zkat/protoduck?branch=latest)
[`protoduck`](https://github.com/zkat/protoduck) is a JavaScript library is a
library for making groups of methods, called "protocols", that work together to
provide some abstract functionality that other things can then rely on. If
you're familiar with the concept of ["duck
library for making groups of methods, called "protocols".
If you're familiar with the concept of ["duck
typing"](https://en.wikipedia.org/wiki/Duck_typing), then it might make sense to

@@ -11,16 +11,5 @@ think of protocols as things that explicitly define what methods you need in

On top of providing a nice, clear interface for defining these protocols, this
module clear, useful errors when implementations are missing something or doing
something wrong.
One thing that sets this library apart from others is that on top of defining
duck-typed protocols on a single class/type, it lets you have different
implementations depending on the _arguments_. So a method on `Foo` may call
different code dependent on whether its first _argument_ is `Bar` or `Baz`. If
you've ever wished a method worked differently for different types of things
passed to it, this does that!
## Install
`$ npm install protoduck`
`$ npm install -S protoduck`

@@ -34,8 +23,8 @@ ## Table of Contents

* [Defining protocols](#defining-protocols)
* [Simple impls](#simple-impls)
* [Implementations](#protocol-impls)
* [Multiple dispatch](#multiple-dispatch)
* [Static impls](#static-impls)
* [Constraints](#constraints)
* [API](#api)
* [`protocol()`](#protocol)
* [`impls`](#impl)
* [`define()`](#define)
* [`proto.impl()`](#impl)

@@ -45,6 +34,6 @@ ### Example

```javascript
import protocol from "protoduck"
const protoduck = require('protoduck')
// Quackable is a protocol that defines three methods
const Quackable = protocol({
const Quackable = protoduck.define({
walk: [],

@@ -66,13 +55,15 @@ talk: [],

// and another place...
// ...In a different package:
const ducks = require('./ducks')
class Duck () {}
// Implement the protocol on the Duck class.
Quackable(Duck, [], {
walk() { return "*hobble hobble*" }
talk() { return "QUACK QUACK" }
ducks.Quackable.impl(Duck, {
walk () { return "*hobble hobble*" }
talk () { return "QUACK QUACK" }
})
// main.js
doStuffToDucks(new Duck()) // works!
ducks.doStuffToDucks(new Duck()) // works!
```

@@ -82,9 +73,8 @@

* Clear, concise protocol definitions and implementations
* Verifies implementations in case methods are missing
* "Static" implementations ("class methods")
* Verifies implementations in case methods are missing or wrong ones added
* Helpful, informative error messages
* Optional default method implementations
* Fresh JavaScript Feel™ -- methods work just like native methods when called
* Methods can dispatch on arguments, not just `this` ([multimethods](npm.im/genfun))
* Fast, cached multiple dispatch
* Methods can dispatch on arguments, not just `this` ([multimethods](https://npm.im/genfun))
* Type constraints

@@ -95,23 +85,26 @@ ### Guide

JavaScript comes with its own method definition mechanism: You simply add
regular `function`s as properties to regular objects, and when you do
`obj.method()`, it calls the right code! ES6/ES2015 further extended this by
adding a `class` syntax that allowed this same system to work with more familiar
syntax sugar: `class Foo { method() { ... } }`.
Like most Object-oriented languages, JavaScript comes with its own way of
defining methods: You simply add regular `function`s as properties to regular
objects, and when you do `obj.method()`, it calls the right code! ES6/ES2015
further extended this by adding a `class` syntax that allowed this same system
to work with more familiar syntax sugar: `class Foo { method() { ... } }`.
`protoduck` is a similar *language extension*: it adds something called
"protocols" to JavaScript.
The point of "protocols" is to have a more explicit definitions of what methods
"go together". That is, a protocol is a description of a type of object your
code interacts with. If someone passes an object into your library, and it fits
your defined protocol, the assumption is that the object will work just as well.
The purpose of protocols is to have a more explicit definitions of what methods
"go together". That is, if you have a type of task, you can group every method
that things definitely need to have under a protocol, and then write your code
using the methods defined there. The assumption is that anything that defines
that group of methods will work with the rest of your code.
Duck typing is a common term for this sort of thing: If it walks like a duck,
and it talks like a duck, then it may as well be a duck, as far as any of our
code is concerned.
And then you can export the protocol itself, and tell your users "if you
implement this protocol for your own objects, they'll work with my code."
Many other languages have similar or identical concepts under different names:
Java's interfaces, Haskell's typeclasses, Rust's traits. Elixir and Clojure both
call them "protocols" as well.
Duck typing is a common term for this: If it walks like a duck, and it talks
like a duck, then it may as well be a duck, as far as any of our code is
concerned.
One big advantage to using these protocols is that they let users define their
own versions of some abstraction, without requiring the type to inherit from
another -- protocols are independent of inheritance, even though they're able to
work together with it. If you've ever found yourself in some sort of inheritance
mess, this is exactly the sort of thing you use to escape it.

@@ -125,7 +118,7 @@ #### Defining Protocols

// import the library first!
import protocol from "protoduck"
const protoduck = require('protoduck')
// `Ducklike` is the name of our protocol. It defines what it means for
// something to be "like a duck", as far as our code is concerned.
const Ducklike = protocol([], {
const Ducklike = protoduck.define([], {
walk: [], // This says that the protocol requires a "walk" method.

@@ -140,3 +133,3 @@ talk: [] // and ducks also need to talk

#### Simple impls
#### Protocol Impls

@@ -148,5 +141,3 @@ The simplest type of definitions for protocols are as regular methods. In this

Implementation syntax is very similar to protocol definitions, but it calls the
protocol itself, instead of `protocol`. It also refers to the type that you want
to implement it *on*:
Implementation syntax is very similar to protocol definitions, using `.impl`:

@@ -157,6 +148,6 @@ ```javascript

// Implementing `Ducklike` for `Dog`s
Ducklike(Dog, [], {
walk() { return '*pads on all fours*' }
talk() { return 'woof woof. I mean "quack" >_>' }
peck(victim) { return 'Can I just bite ' + victim + ' instead?...' }
Ducklike.impl(Dog, [], {
walk () { return '*pads on all fours*' }
talk () { return 'woof woof. I mean "quack" >_>' }
peck (victim) { return 'Can I just bite ' + victim + ' instead?...' }
})

@@ -186,7 +177,6 @@ ```

This type of method is called a multimethod, and isn't a core JavaScript
feature. It's something that sets `protoduck` apart, and it can be really
useful!
This type of method is called a multimethod, and is one of the differences
between protoduck and the default `class` syntax.
The way to use it is simple: in the protocol *definitions*, you put matching
To use it: in the protocol *definitions*, you put matching
strings in different spots where those empty arrays were, and when you

@@ -198,4 +188,4 @@ *implement* the protocol, you give the definition the actual types/objects you

```javascript
const Playable = protocol(['friend'], {
playWith: ['friend']
const Playful = protoduck.define(['friend'], {// <---\
playWith: ['friend'] // <------------ these correspond to each other
})

@@ -208,4 +198,4 @@

// The first protocol is for Cat/Human combination
Playable(Cat, [Human], {
playWith(human) {
Playful.impl(Cat, [Human], {
playWith (human) {
return '*headbutt* *purr* *cuddle* omg ilu, ' + human.name

@@ -216,4 +206,4 @@ }

// And we define it *again* for a different combination
Playable(Cat, [Dog], {
playWith(dog) {
Playful.impl(Cat, [Dog], {
playWith (dog) {
return '*scratches* *hisses* omg i h8 u, ' + dog.name

@@ -232,47 +222,42 @@ }

In general, most implementations you write won't care what types their arguments
are, but when you do? This may end up saving you a lot of trouble and allowing
some tricks you might realize you can do that weren't possible before!
#### Constraints
#### Static impls
Sometimes, you want to have all the functionality of a certain protocol, but you
want to add a few requirements or other bits an pieces. Usually, you would have
to define the entire functionality of the "parent" protocol in your own protocol
in order to pull this off. This isn't very DRY and thus prone to errors, missing
or out-of-sync functionality, or other issues. You could also just tell users
"hey, if you implement this, make sure to implement that", but there's no
guarantee they'll know about it, or know which arguments map to what.
Finally, there's a type of protocol impl that doesn't involve a `this` value at
all: static impls. Some languages might call them "class methods" as well. In
the case of `protoduck`, though, these static methods exist on the protocol
object itself, rather than a regular JavaScript class.
This is where constraints come in: You can define a protocol that expects
anything that implements it to *also* implement one or more "parent" protocols.
Static methods can be really useful when you want to call them as plain old
functions without having to worry about the `this` value. And because
`protoduck` supports multiple dispatch, it means you can get full method
functionality, but with regular functions that don't need `this`: they just
operate on their standard arguments.
Static impls are easy to make: simply omit the first object type and use the
arguments array to define what the methods will be implemented on:
```javascript
const Eq = protocol(['a', 'b'], {
equals: ['a', 'b']
})
const equals = Eq.equals // This isn't necessary, but it shows that we
// don't need a `.` to call them, at all!
Eq([Number, Number], {
equals(x, y) {
return x === y
const Show = proto.define({
// This syntax allows default impls without using arrays.
toString () {
return Object.prototype.toString.call(this)
},
toJSON () {
return JSON.stringify(this)
}
})
Eq([Cat, Cat], {
equals(kitty, cat) {
return kitty.name === cat.name
}
const Log = proto.define({
log () { console.log(this.toString()) }
}, {
where: Show()
// Also valid:
// [Show('this'), Show('a')]
// [Show('this', ['a', 'b'])]
})
equals(1, 1) // true
equals(1, 2) // false
equals(snookums, reika) // false
equals(walter, walter) // true
equals(1, snookums) // Error! No protocol impl!
// This fails with an error: must implement Show:
Log.impl(MyThing)
// So derive Show first...
Show.impl(MyThing)
// And now it's ok!
Log.impl(MyThing)
```

@@ -282,3 +267,3 @@

#### <a name="protocol"></a> `protocol(<types>?, <spec>)`
#### <a name="define"></a> `define(<types>?, <spec>, <opts>)`

@@ -291,34 +276,34 @@ Defines a new protocol on across arguments of types defined by `<types>`, which

The types in `<spec>` must map, by string name, to the type names specified in
`<types>`, or be an empty array if `<types>` is omitted. The types in `<spec>`
will then be used to map between method implementations for the individual
functions, and the provided types in the impl.
The types in `<spec>` entries must map, by string name, to the type names
specified in `<types>`, or be an empty array if `<types>` is omitted. The types
in `<spec>` will then be used to map between method implementations for the
individual functions, and the provided types in the impl.
Protocols can include an `opts` object as the last argument, with the following
available options:
* `opts.name` `{String}` - The name to use when referring to the protocol.
* `opts.where` `{Array[Constraint]|Constraint}` - Protocol constraints to use.
* `opts.metaobject` - Accepts an object implementing the
`Protoduck` protocol, which can be used to alter protocol definition
mechanisms in `protoduck`.
##### Example
```javascript
const Eq = protocol(['a', 'b'], {
eq: ['a', 'b']
const Eq = protoduck.define(['a'], {
eq: ['a']
})
```
#### <a name="impl"></a> `proto(<target>?, <types>?, <implementations>)`
#### <a name="impl"></a> `proto.impl(<target>, <types>?, <implementations>?)`
Adds a new implementation to the given `proto` across `<types>`.
Adds a new implementation to the given protocol across `<types>`.
`<implementations>` must be an object with functions matching the protocol's
API. The types in `<types>` will be used for defining specific methods using
the function as the body.
API. If given, the types in `<types>` will be mapped to their corresponding
method arguments according to the original protocol definition.
Protocol implementations must include either `<target>`, `<types>`, or both:
* If only `<target>` is present, implementations will be defined the same as
"traditional" methods -- that is, the definitions in `<implementations>`
will add function properties directly to `<target>`.
* If only `<types>` is present, the protocol will keep all protocol functions as
"static" methods on the protocol itself.
* If both are specified, protocol implementations will add methods to the `<target>`, and define multimethods using `<types>`.
If a protocol is derivable -- that is, all its functions have default impls,

@@ -331,17 +316,20 @@ then the `<implementations>` object can be omitted entirely, and the protocol

```javascript
import protocol from 'protoduck'
import protoduck from 'protoduck'
// Singly-dispatched protocols
const Show = protocol({
const Show = protoduck.define({
show: []
})
class Foo {}
class Foo {
constructor (name) {
this.name = name
}
}
Show(Foo, {
Show.impl(Foo, {
show () { return `[object Foo(${this.name})]` }
})
var f = new Foo()
f.name = 'alex'
const f = new Foo('alex')
f.show() === '[object Foo(alex)]'

@@ -351,6 +339,6 @@ ```

```javascript
import protocol from 'protoduck'
import protoduck from 'protoduck'
// Multi-dispatched protocols
const Comparable = protocol(['target'], {
const Comparable = protoduck.define(['target'], {
compare: ['target'],

@@ -363,7 +351,7 @@ })

Comparable(Foo, [Bar], {
Comparable.impl(Foo, [Bar], {
compare (bar) { return 'bars are ok' }
})
Comparable(Foo, [Baz], {
Comparable.impl(Foo, [Baz], {
compare (baz) { return 'but bazzes are better' }

@@ -370,0 +358,0 @@ })

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc