UNPKG

7.08 kBMarkdownView Raw
1# Genfun [![Travis](https://img.shields.io/travis/zkat/genfun.svg)](https://travis-ci.org/zkat/genfun) [![npm](https://img.shields.io/npm/v/genfun.svg)](https://npm.im/genfun) [![npm](https://img.shields.io/npm/l/genfun.svg)](https://npm.im/genfun)
2
3[`genfun`](https://github.com/zkat/genfun) is a Javascript library that lets you
4define generic functions: regular-seeming functions that can be invoked just
5like any other function, but that automatically dispatch methods based on the
6combination of arguments passed to it when it's called, also known as multiple
7dispatch.
8
9It was inspired by [Slate](http://slatelanguage.org/),
10[CLOS](http://en.wikipedia.org/wiki/CLOS) and
11[Sheeple](http://github.com/zkat/sheeple).
12
13## Install
14
15`$ npm install genfun`
16
17## Table of Contents
18
19* [Example](#example)
20* [API](#api)
21 * [`Genfun()`](#genfun)
22 * [`gf.add()`](#addMethod)
23 * [`Genfun.callNextMethod()`](#callNextMethod)
24 * [`Genfun.noApplicableMethod()`](#noApplicableMethod)
25* [Performance](#performance)
26
27### Example
28
29Various examples are available to look at in the examples/ folder included in
30this project. Most examples are also runnable by just invoking them with node.
31
32```javascript
33import Genfun from "genfun"
34
35class Person {}
36class Dog {}
37
38const frobnicate = Genfun()
39
40frobnicate.add([Person], (person) => {
41 console.log('Got a person!')
42})
43
44frobnicate.add([Dog], (dog) => {
45 console.log('Got a dog!')
46})
47
48frobnicate.add([String, Person, Dog], (greeting, person, dog) => {
49 console.log(person, ' greets ', dog, ', \'' + greeting + '\'')
50})
51
52const person = new Person()
53const dog = new Dog()
54
55frobnicate(person) // Got a person!
56frobnicate(dog) // Got a dog!
57frobnicate('Hi, dog!', person, dog); // {} greets {}, 'Hi, dog!'
58```
59
60### API
61
62The basic API for `Genfun` is fairly simple: You create a new `genfun` by
63calling `Genfun()`, and add methods to them. Then you call the `genfun` object
64like a regular function, and it takes care of dispatching the appropriate
65methods!
66
67#### `Genfun()`
68
69Takes no arguments. Simply creates a new `genfun`. A `genfun` is a regular
70function object with overriden function call/dispatch behavior.
71
72When called, it will look at its arguments and determine if a matching method
73has been defined that applies to **all** arguments passed in, considered
74together.
75
76New methods may be added to the `genfun` object with [`gf.add()`](#addMethod).
77
78If no method is found, or none has been defined, it will invoke
79[`Genfun.noApplicableMethod`](#noApplicableMethod) with the appropriate
80arguments.
81
82Genfuns preserve the value of `this` if invoked using `.call` or `.apply`.
83
84##### Example
85
86```javascript
87var gf = Genfun()
88
89//... add some methods ..
90
91// These calls are all identical.
92gf(1, 2, 3)
93gf.call(null, 1, 2, 3)
94gf.apply(null, [1, 2, 3])
95```
96
97#### <a name="addMethod"></a> `gf.add(<selector>, <body>)`
98
99Adds a new method to `gf` and returns `gf` to allow chaining multiple `add`s.
100
101`<selector>` must be an array of objects that will receive new `Role`s (dispatch
102positions) for the method. If an object in the selector is a function, its
103`.prototype` field will receive the new `Role`. The array must not contain any
104frozen objects.
105
106When a `genfun` is called (like a function), it will look at its set of added
107methods and, based on the `Role`s assigned, and corresponding prototype chains,
108will determine which method, if any, will be invoked. On invocation, a method's
109`<body>` argument will be the called with the arguments passed to the `genfun`,
110including its `this` and `arguments` values`.
111
112Within the `<body>`, [`Genfun.callNextMethod`](#callNextMethod) may be called.
113
114##### Example
115
116```javascript
117
118var numStr = Genfun()
119
120numStr.add([String, Number], function (str, num) {
121 console.log('got a str:', str, 'and a num: ', num)
122})
123
124numStr.add([Number, String], function (num, str) {
125 console.log('got a num:', num, 'and a str:', str)
126})
127
128```
129
130#### <a name="callNextMethod"></a> `Genfun.callNextMethod([...<arguments>])`
131
132**NOTE**: This function can only be called synchronously. To call it
133asynchronously (for example, in a `Promise` or in a callback), use
134[`getContext`](#getContext)
135
136Calls the "next" applicable method in the method chain. Can only be called
137within the body of a method.
138
139If no arguments are given, `callNextMethod` will pass the current method's
140original arguments to the next method.
141
142If arguments are passed to `callNextMethod`, it will invoke the next applicable
143method (based on the **original** method list calculation), with **the given
144arguments**, even if they would otherwise not have triggered that method.
145
146Returns whatever value the next method returns.
147
148There **must** be a next method available when invoked. This function **will
149not** call `noApplicableMethod` when it runs out of methods to call. It will
150instead throw an error.
151
152##### Example
153
154```javascript
155class Foo {}
156class Bar extends Foo {}
157
158var cnm = Genfun()
159
160cnm.add([Foo], function (foo) {
161 console.log('calling the method on Foo with', foo)
162 return foo
163})
164
165cnm.add([Bar], function (bar) {
166 console.log('calling the method on Bar with', bar)
167 return Genfun.callNextMethod('some other value!')
168})
169
170cnm(new Bar())
171// calling the method on Bar with {}
172// calling the method on Foo with "some other value!"
173// => 'some other value!'
174```
175
176#### <a name="getContext"></a> `Genfun.getContext()`
177
178The `context` returned by this function will have a `callNextMethod` method
179which can be used to invoke the correct next method even during asynchronous
180calls (for example, when used in a callback or a `Promise`).
181
182This function must be called synchronously within the body of the method before
183any asynchronous calls, and will error if invoked outside the context of a
184method call.
185
186##### Example
187
188```javascript
189someGenfun.add([MyThing], function (thing) {
190 const ctx = Genfun.getContext()
191 return somePromisedCall(thing).then(res => ctx.callNextMethod(res))
192})
193```
194
195#### <a name="noApplicableMethod"></a> `Genfun.noApplicableMethod(<gf>, <this>, <args>)`
196
197`Genfun.noApplicableMethod` is a `genfun` itself, which is called whenever **any `genfun`** fails to find a matching method for its given arguments.
198
199It will be called with the `genfun` as its first argument, then the `this`
200value, and then the arguments it was called with.
201
202By default, this will simply throw a NoApplicableMethod error.
203
204Users may override this behavior for particular `genfun` and `this`
205combinations, although `args` will always be an `Array`. The value returned from
206the dispatched `noApplicableMethod` method will be returned by `genfun` as if it
207had been its original method. Comparable to [Ruby's
208`method_missing`](http://ruby-doc.org/core-2.1.0/BasicObject.html#method-i-method_missing).
209
210### Performance
211
212`Genfun` pulls a few caching tricks to make sure dispatch, specially for common
213cases, is as fast as possible.
214
215How fast? Well, not much slower than native methods:
216
217```
218Regular function: 30.402ms
219Native method: 28.109ms
220Singly-dispatched genfun: 64.467ms
221Double-dispatched genfun: 70.052ms
222Double-dispatched genfun with string primitive: 76.742ms
223```