Comparing version 3.0.0 to 3.0.1
14
index.js
@@ -56,8 +56,10 @@ 'use strict' | ||
'No ' + (proto.name || 'protocol') + ' impl for `' + | ||
(target ? typeName(thisArg) + '#' : '') + | ||
name + | ||
'` found for arguments of types: (' + | ||
[].map.call(args, typeName).join(', ') + ')' | ||
if (target) { | ||
msg += ' and `this` type ' + typeName(thisArg) | ||
} | ||
'(' + | ||
[].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) | ||
@@ -116,3 +118,3 @@ err.protocol = proto | ||
var methodTypes = calculateMethodTypes(name, proto, types) | ||
if (target && !target[name]) { | ||
if (target != null && !{}.hasOwnProperty.call(target, name)) { | ||
target[name] = proto._metaobject | ||
@@ -119,0 +121,0 @@ ? Protocol.meta.createGenfun(proto._metaobject, proto, target, name) |
{ | ||
"name": "protoduck", | ||
"version": "3.0.0", | ||
"version": "3.0.1", | ||
"description": "Fancy duck typing for the most serious of ducks.", | ||
@@ -11,3 +11,5 @@ "main": "index.js", | ||
"preversion": "npm t", | ||
"test": "standard && nyc -- mocha --reporter spec" | ||
"postversion": "npm publish && git push --follow-tags", | ||
"pretest": "standard", | ||
"test": "nyc -- mocha --reporter spec" | ||
}, | ||
@@ -43,3 +45,3 @@ "repository": { | ||
"dependencies": { | ||
"genfun": "^3.0.0" | ||
"genfun": "^3.0.1" | ||
}, | ||
@@ -46,0 +48,0 @@ "devDependencies": { |
220
README.md
@@ -1,2 +0,2 @@ | ||
# Protoduck [![Travis](https://img.shields.io/travis/zkat/protoduck.svg)](https://travis-ci.org/zkat/protoduck) [![npm version](https://img.shields.io/npm/v/@zkat/protoduck.svg)](https://npm.im/@zkat/protoduck) [![license](https://img.shields.io/npm/l/@zkat/protoduck.svg)](https://npm.im/@zkat/protoduck) | ||
# 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) | ||
@@ -29,5 +29,12 @@ [`protoduck`](https://github.com/zkat/protoduck) is a JavaScript library is a | ||
* [Example](#example) | ||
* [Features](#features) | ||
* [Guide](#guide) | ||
* [Introduction](#introduction) | ||
* [Defining protocols](#defining-protocols) | ||
* [Simple impls](#simple-impls) | ||
* [Multiple dispatch](#multiple-dispatch) | ||
* [Static impls](#static-impls) | ||
* [API](#api) | ||
* [`protocol()`](#protocol) | ||
* [`implementation`](#impl) | ||
* [`impls`](#impl) | ||
@@ -43,3 +50,3 @@ ### Example | ||
talk: [], | ||
isADuck: [] | ||
isADuck: [() => true] // default implementation -- it's optional! | ||
}) | ||
@@ -58,26 +65,204 @@ | ||
// elsewhere in the project... | ||
class Person () {} | ||
Quackable(Person, { | ||
walk() { return "my knees go the wrong way but I'm doing my best" } | ||
talk() { return "uhhh... do I have to? oh... 'Quack' 😒"} | ||
isADuck() { return true /* lol I'm totally lying */ } | ||
}) | ||
// and another place... | ||
class Duck () {} | ||
Quackable(Duck, { | ||
// Implement the protocol on the Duck class. | ||
Quackable(Duck, [], { | ||
walk() { return "*hobble hobble*" } | ||
talk() { return "QUACK QUACK" } | ||
isADuck() { return true } | ||
}) | ||
// main.js | ||
doStuffToDucks(new Person()) // works | ||
doStuffToDucks(new Duck()) // works | ||
doStuffToDucks({ walk() { return 'meh' } }) // => error | ||
doStuffToDucks(new Duck()) // works! | ||
``` | ||
### Features | ||
* Clear, concise protocol definitions and implementations | ||
* Verifies implementations in case methods are missing | ||
* "Static" implementations ("class methods") | ||
* 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 | ||
### Guide | ||
#### Introduction | ||
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() { ... } }`. | ||
`protoduck` is a similar *language extension*: it adds something called | ||
"protocols" to JavaScript. | ||
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. | ||
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." | ||
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. | ||
#### Defining Protocols | ||
The first step to using `protoduck` is to define a protocol. Protocol | ||
definitions look like this: | ||
```javascript | ||
// import the library first! | ||
import protocol from "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([], { | ||
walk: [], // This says that the protocol requires a "walk" method. | ||
talk: [] // and ducks also need to talk | ||
peck: [] // and they can even be pretty scary | ||
}) | ||
``` | ||
Protocols by themselves don't really *do* anything, they simply define what | ||
methods are included in the protocol, and thus what will need to be implemented. | ||
#### Simple impls | ||
The simplest type of definitions for protocols are as regular methods. In this | ||
style, protocols end up working exactly like normal JavaScript methods: they're | ||
added as properties of the target type/object, and we call them using the | ||
`foo.method()` syntax. `this` is accessible inside the methods, as usual. | ||
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*: | ||
```javascript | ||
class Dog {} | ||
// 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?...' } | ||
}) | ||
``` | ||
So now, our `Dog` class has two extra methods: `walk`, and `talk`, and we can | ||
just call them: | ||
```javascript | ||
const pupper = new Dog() | ||
pupper.walk() // *pads on all fours* | ||
pupper.talk() // woof woof. I mean "quack" >_> | ||
pupper.peck('this string') // Can I just bite this string instead?... | ||
``` | ||
#### Multiple Dispatch | ||
You may have noticed before that we have these `[]` in various places that don't | ||
seem to have any obvious purpose. | ||
These arrays allow protocols to be implemented not just for a single value of | ||
`this`, but across *all arguments*. That is, you can have methods in these | ||
protocols that use both `this`, and the first argument (or any other arguments) | ||
in order to determine what code to actually execute. | ||
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! | ||
The way to use it is simple: in the protocol *definitions*, you put matching | ||
strings in different spots where those empty arrays were, and when you | ||
*implement* the protocol, you give the definition the actual types/objects you | ||
want to implement it on, and it takes care of mapping types to the strings you | ||
defined, and making sure the right code is run: | ||
```javascript | ||
const Playable = protocol(['friend'], { | ||
playWith: ['friend'] | ||
}) | ||
class Cat {} | ||
class Human {} | ||
class Dog {} | ||
// The first protocol is for Cat/Human combination | ||
Playable(Cat, [Human], { | ||
playWith(human) { | ||
return '*headbutt* *purr* *cuddle* omg ilu, ' + human.name | ||
} | ||
}) | ||
// And we define it *again* for a different combination | ||
Playable(Cat, [Dog], { | ||
playWith(dog) { | ||
return '*scratches* *hisses* omg i h8 u, ' + dog.name | ||
} | ||
}) | ||
// depending on what you call it with, it runs different methods: | ||
const cat = new Cat() | ||
const human = new Human() | ||
const dog = new Dog() | ||
cat.playWith(human) // *headbutt* *purr* *cuddle* omg ilu, Sam | ||
cat.playWith(dog) // *scratches* *hisses* omg i h8 u, Pupper | ||
``` | ||
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! | ||
#### Static impls | ||
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. | ||
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 | ||
} | ||
}) | ||
Eq([Cat, Cat], { | ||
equals(kitty, cat) { | ||
return kitty.name === cat.name | ||
} | ||
}) | ||
equals(1, 1) // true | ||
equals(1, 2) // false | ||
equals(snookums, reika) // false | ||
equals(walter, walter) // true | ||
equals(1, snookums) // Error! No protocol impl! | ||
``` | ||
### API | ||
@@ -93,3 +278,2 @@ | ||
The types in `<spec>` must map, by string name, to the type names specified in | ||
@@ -96,0 +280,0 @@ `<types>`, or be an empty array if `<types>` is omitted. The types in `<spec>` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
17434
141
359
Updatedgenfun@^3.0.1