README.md
1# 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)
2
3[`protoduck`](https://github.com/zkat/protoduck) is a JavaScript library is a
4library for making groups of methods, called "protocols".
5
6If you're familiar with the concept of ["duck
7typing"](https://en.wikipedia.org/wiki/Duck_typing), then it might make sense to
8think of protocols as things that explicitly define what methods you need in
9order to "clearly be a duck".
10
11## Install
12
13`$ npm install -S protoduck`
14
15## Table of Contents
16
17* [Example](#example)
18* [Features](#features)
19* [Guide](#guide)
20 * [Introduction](#introduction)
21 * [Defining protocols](#defining-protocols)
22 * [Implementations](#protocol-impls)
23 * [Multiple dispatch](#multiple-dispatch)
24 * [Constraints](#constraints)
25* [API](#api)
26 * [`define()`](#define)
27 * [`proto.impl()`](#impl)
28
29### Example
30
31```javascript
32const protoduck = require('protoduck')
33
34// Quackable is a protocol that defines three methods
35const Quackable = protoduck.define({
36 walk: [],
37 talk: [],
38 isADuck: [() => true] // default implementation -- it's optional!
39})
40
41// `duck` must implement `Quackable` for this function to work. It doesn't
42// matter what type or class duck is, as long as it implements Quackable.
43function doStuffToDucks (duck) {
44 if (!duck.isADuck()) {
45 throw new Error('I want a duck!')
46 } else {
47 console.log(duck.walk())
48 console.log(duck.talk())
49 }
50}
51
52// ...In a different package:
53const ducks = require('./ducks')
54
55class Duck () {}
56
57// Implement the protocol on the Duck class.
58ducks.Quackable.impl(Duck, {
59 walk () { return "*hobble hobble*" }
60 talk () { return "QUACK QUACK" }
61})
62
63// main.js
64ducks.doStuffToDucks(new Duck()) // works!
65```
66
67### Features
68
69* Verifies implementations in case methods are missing or wrong ones added
70* Helpful, informative error messages
71* Optional default method implementations
72* Fresh JavaScript Feel™ -- methods work just like native methods when called
73* Methods can dispatch on arguments, not just `this` ([multimethods](https://npm.im/genfun))
74* Type constraints
75
76### Guide
77
78#### Introduction
79
80Like most Object-oriented languages, JavaScript comes with its own way of
81defining methods: You simply add regular `function`s as properties to regular
82objects, and when you do `obj.method()`, it calls the right code! ES6/ES2015
83further extended this by adding a `class` syntax that allowed this same system
84to work with more familiar syntax sugar: `class Foo { method() { ... } }`.
85
86The point of "protocols" is to have a more explicit definitions of what methods
87"go together". That is, a protocol is a description of a type of object your
88code interacts with. If someone passes an object into your library, and it fits
89your defined protocol, the assumption is that the object will work just as well.
90
91Duck typing is a common term for this sort of thing: If it walks like a duck,
92and it talks like a duck, then it may as well be a duck, as far as any of our
93code is concerned.
94
95Many other languages have similar or identical concepts under different names:
96Java's interfaces, Haskell's typeclasses, Rust's traits. Elixir and Clojure both
97call them "protocols" as well.
98
99One big advantage to using these protocols is that they let users define their
100own versions of some abstraction, without requiring the type to inherit from
101another -- protocols are independent of inheritance, even though they're able to
102work together with it. If you've ever found yourself in some sort of inheritance
103mess, this is exactly the sort of thing you use to escape it.
104
105#### Defining Protocols
106
107The first step to using `protoduck` is to define a protocol. Protocol
108definitions look like this:
109
110```javascript
111// import the library first!
112const protoduck = require('protoduck')
113
114// `Ducklike` is the name of our protocol. It defines what it means for
115// something to be "like a duck", as far as our code is concerned.
116const Ducklike = protoduck.define([], {
117 walk: [], // This says that the protocol requires a "walk" method.
118 talk: [] // and ducks also need to talk
119 peck: [] // and they can even be pretty scary
120})
121```
122
123Protocols by themselves don't really *do* anything, they simply define what
124methods are included in the protocol, and thus what will need to be implemented.
125
126#### Protocol Impls
127
128The simplest type of definitions for protocols are as regular methods. In this
129style, protocols end up working exactly like normal JavaScript methods: they're
130added as properties of the target type/object, and we call them using the
131`foo.method()` syntax. `this` is accessible inside the methods, as usual.
132
133Implementation syntax is very similar to protocol definitions, using `.impl`:
134
135```javascript
136class Dog {}
137
138// Implementing `Ducklike` for `Dog`s
139Ducklike.impl(Dog, [], {
140 walk () { return '*pads on all fours*' }
141 talk () { return 'woof woof. I mean "quack" >_>' }
142 peck (victim) { return 'Can I just bite ' + victim + ' instead?...' }
143})
144```
145
146So now, our `Dog` class has two extra methods: `walk`, and `talk`, and we can
147just call them:
148
149```javascript
150const pupper = new Dog()
151
152pupper.walk() // *pads on all fours*
153pupper.talk() // woof woof. I mean "quack" >_>
154pupper.peck('this string') // Can I just bite this string instead?...
155```
156
157#### Multiple Dispatch
158
159You may have noticed before that we have these `[]` in various places that don't
160seem to have any obvious purpose.
161
162These arrays allow protocols to be implemented not just for a single value of
163`this`, but across *all arguments*. That is, you can have methods in these
164protocols that use both `this`, and the first argument (or any other arguments)
165in order to determine what code to actually execute.
166
167This type of method is called a multimethod, and is one of the differences
168between protoduck and the default `class` syntax.
169
170To use it: in the protocol *definitions*, you put matching
171strings in different spots where those empty arrays were, and when you
172*implement* the protocol, you give the definition the actual types/objects you
173want to implement it on, and it takes care of mapping types to the strings you
174defined, and making sure the right code is run:
175
176```javascript
177const Playful = protoduck.define(['friend'], {// <---\
178 playWith: ['friend'] // <------------ these correspond to each other
179})
180
181class Cat {}
182class Human {}
183class Dog {}
184
185// The first protocol is for Cat/Human combination
186Playful.impl(Cat, [Human], {
187 playWith (human) {
188 return '*headbutt* *purr* *cuddle* omg ilu, ' + human.name
189 }
190})
191
192// And we define it *again* for a different combination
193Playful.impl(Cat, [Dog], {
194 playWith (dog) {
195 return '*scratches* *hisses* omg i h8 u, ' + dog.name
196 }
197})
198
199// depending on what you call it with, it runs different methods:
200const cat = new Cat()
201const human = new Human()
202const dog = new Dog()
203
204cat.playWith(human) // *headbutt* *purr* *cuddle* omg ilu, Sam
205cat.playWith(dog) // *scratches* *hisses* omg i h8 u, Pupper
206```
207
208#### Constraints
209
210Sometimes, you want to have all the functionality of a certain protocol, but you
211want to add a few requirements or other bits an pieces. Usually, you would have
212to define the entire functionality of the "parent" protocol in your own protocol
213in order to pull this off. This isn't very DRY and thus prone to errors, missing
214or out-of-sync functionality, or other issues. You could also just tell users
215"hey, if you implement this, make sure to implement that", but there's no
216guarantee they'll know about it, or know which arguments map to what.
217
218This is where constraints come in: You can define a protocol that expects
219anything that implements it to *also* implement one or more "parent" protocols.
220
221```javascript
222const Show = proto.define({
223 // This syntax allows default impls without using arrays.
224 toString () {
225 return Object.prototype.toString.call(this)
226 },
227 toJSON () {
228 return JSON.stringify(this)
229 }
230})
231
232const Log = proto.define({
233 log () { console.log(this.toString()) }
234}, {
235 where: Show()
236 // Also valid:
237 // [Show('this'), Show('a')]
238 // [Show('this', ['a', 'b'])]
239})
240
241// This fails with an error: must implement Show:
242Log.impl(MyThing)
243
244// So derive Show first...
245Show.impl(MyThing)
246// And now it's ok!
247Log.impl(MyThing)
248```
249
250### API
251
252#### <a name="define"></a> `define(<types>?, <spec>, <opts>)`
253
254Defines a new protocol on across arguments of types defined by `<types>`, which
255will expect implementations for the functions specified in `<spec>`.
256
257If `<types>` is missing, it will be treated the same as if it were an empty
258array.
259
260The types in `<spec>` entries must map, by string name, to the type names
261specified in `<types>`, or be an empty array if `<types>` is omitted. The types
262in `<spec>` will then be used to map between method implementations for the
263individual functions, and the provided types in the impl.
264
265Protocols can include an `opts` object as the last argument, with the following
266available options:
267
268* `opts.name` `{String}` - The name to use when referring to the protocol.
269
270* `opts.where` `{Array[Constraint]|Constraint}` - Protocol constraints to use.
271
272* `opts.metaobject` - Accepts an object implementing the
273 `Protoduck` protocol, which can be used to alter protocol definition
274 mechanisms in `protoduck`.
275
276##### Example
277
278```javascript
279const Eq = protoduck.define(['a'], {
280 eq: ['a']
281})
282```
283
284#### <a name="impl"></a> `proto.impl(<target>, <types>?, <implementations>?)`
285
286Adds a new implementation to the given protocol across `<types>`.
287
288`<implementations>` must be an object with functions matching the protocol's
289API. If given, the types in `<types>` will be mapped to their corresponding
290method arguments according to the original protocol definition.
291
292If a protocol is derivable -- that is, all its functions have default impls,
293then the `<implementations>` object can be omitted entirely, and the protocol
294will be automatically derived for the given `<types>`
295
296##### Example
297
298```javascript
299import protoduck from 'protoduck'
300
301// Singly-dispatched protocols
302const Show = protoduck.define({
303 show: []
304})
305
306class Foo {
307 constructor (name) {
308 this.name = name
309 }
310}
311
312Show.impl(Foo, {
313 show () { return `[object Foo(${this.name})]` }
314})
315
316const f = new Foo('alex')
317f.show() === '[object Foo(alex)]'
318```
319
320```javascript
321import protoduck from 'protoduck'
322
323// Multi-dispatched protocols
324const Comparable = protoduck.define(['target'], {
325 compare: ['target'],
326})
327
328class Foo {}
329class Bar {}
330class Baz {}
331
332Comparable.impl(Foo, [Bar], {
333 compare (bar) { return 'bars are ok' }
334})
335
336Comparable.impl(Foo, [Baz], {
337 compare (baz) { return 'but bazzes are better' }
338})
339
340const foo = new Foo()
341const bar = new Bar()
342const baz = new Baz()
343
344foo.compare(bar) // 'bars are ok'
345foo.compare(baz) // 'but bazzes are better'
346```
347