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
|
4 | define generic functions: regular-seeming functions that can be invoked just
|
5 | like any other function, but that automatically dispatch methods based on the
|
6 | combination of arguments passed to it when it's called, also known as multiple
|
7 | dispatch.
|
8 |
|
9 | It 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 |
|
29 | Various examples are available to look at in the examples/ folder included in
|
30 | this project. Most examples are also runnable by just invoking them with node.
|
31 |
|
32 | ```javascript
|
33 | import Genfun from "genfun"
|
34 |
|
35 | class Person {}
|
36 | class Dog {}
|
37 |
|
38 | const frobnicate = Genfun()
|
39 |
|
40 | frobnicate.add([Person], (person) => {
|
41 | console.log('Got a person!')
|
42 | })
|
43 |
|
44 | frobnicate.add([Dog], (dog) => {
|
45 | console.log('Got a dog!')
|
46 | })
|
47 |
|
48 | frobnicate.add([String, Person, Dog], (greeting, person, dog) => {
|
49 | console.log(person, ' greets ', dog, ', \'' + greeting + '\'')
|
50 | })
|
51 |
|
52 | const person = new Person()
|
53 | const dog = new Dog()
|
54 |
|
55 | frobnicate(person) // Got a person!
|
56 | frobnicate(dog) // Got a dog!
|
57 | frobnicate('Hi, dog!', person, dog); // {} greets {}, 'Hi, dog!'
|
58 | ```
|
59 |
|
60 | ### API
|
61 |
|
62 | The basic API for `Genfun` is fairly simple: You create a new `genfun` by
|
63 | calling `Genfun()`, and add methods to them. Then you call the `genfun` object
|
64 | like a regular function, and it takes care of dispatching the appropriate
|
65 | methods!
|
66 |
|
67 | #### `Genfun()`
|
68 |
|
69 | Takes no arguments. Simply creates a new `genfun`. A `genfun` is a regular
|
70 | function object with overriden function call/dispatch behavior.
|
71 |
|
72 | When called, it will look at its arguments and determine if a matching method
|
73 | has been defined that applies to **all** arguments passed in, considered
|
74 | together.
|
75 |
|
76 | New methods may be added to the `genfun` object with [`gf.add()`](#addMethod).
|
77 |
|
78 | If no method is found, or none has been defined, it will invoke
|
79 | [`Genfun.noApplicableMethod`](#noApplicableMethod) with the appropriate
|
80 | arguments.
|
81 |
|
82 | Genfuns preserve the value of `this` if invoked using `.call` or `.apply`.
|
83 |
|
84 | ##### Example
|
85 |
|
86 | ```javascript
|
87 | var gf = Genfun()
|
88 |
|
89 | //... add some methods ..
|
90 |
|
91 | // These calls are all identical.
|
92 | gf(1, 2, 3)
|
93 | gf.call(null, 1, 2, 3)
|
94 | gf.apply(null, [1, 2, 3])
|
95 | ```
|
96 |
|
97 | #### <a name="addMethod"></a> `gf.add(<selector>, <body>)`
|
98 |
|
99 | Adds 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
|
102 | positions) 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
|
104 | frozen objects.
|
105 |
|
106 | When a `genfun` is called (like a function), it will look at its set of added
|
107 | methods and, based on the `Role`s assigned, and corresponding prototype chains,
|
108 | will 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`,
|
110 | including its `this` and `arguments` values`.
|
111 |
|
112 | Within the `<body>`, [`Genfun.callNextMethod`](#callNextMethod) may be called.
|
113 |
|
114 | ##### Example
|
115 |
|
116 | ```javascript
|
117 |
|
118 | var numStr = Genfun()
|
119 |
|
120 | numStr.add([String, Number], function (str, num) {
|
121 | console.log('got a str:', str, 'and a num: ', num)
|
122 | })
|
123 |
|
124 | numStr.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
|
133 | asynchronously (for example, in a `Promise` or in a callback), use
|
134 | [`getContext`](#getContext)
|
135 |
|
136 | Calls the "next" applicable method in the method chain. Can only be called
|
137 | within the body of a method.
|
138 |
|
139 | If no arguments are given, `callNextMethod` will pass the current method's
|
140 | original arguments to the next method.
|
141 |
|
142 | If arguments are passed to `callNextMethod`, it will invoke the next applicable
|
143 | method (based on the **original** method list calculation), with **the given
|
144 | arguments**, even if they would otherwise not have triggered that method.
|
145 |
|
146 | Returns whatever value the next method returns.
|
147 |
|
148 | There **must** be a next method available when invoked. This function **will
|
149 | not** call `noApplicableMethod` when it runs out of methods to call. It will
|
150 | instead throw an error.
|
151 |
|
152 | ##### Example
|
153 |
|
154 | ```javascript
|
155 | class Foo {}
|
156 | class Bar extends Foo {}
|
157 |
|
158 | var cnm = Genfun()
|
159 |
|
160 | cnm.add([Foo], function (foo) {
|
161 | console.log('calling the method on Foo with', foo)
|
162 | return foo
|
163 | })
|
164 |
|
165 | cnm.add([Bar], function (bar) {
|
166 | console.log('calling the method on Bar with', bar)
|
167 | return Genfun.callNextMethod('some other value!')
|
168 | })
|
169 |
|
170 | cnm(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 |
|
178 | The `context` returned by this function will have a `callNextMethod` method
|
179 | which can be used to invoke the correct next method even during asynchronous
|
180 | calls (for example, when used in a callback or a `Promise`).
|
181 |
|
182 | This function must be called synchronously within the body of the method before
|
183 | any asynchronous calls, and will error if invoked outside the context of a
|
184 | method call.
|
185 |
|
186 | ##### Example
|
187 |
|
188 | ```javascript
|
189 | someGenfun.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 |
|
199 | It will be called with the `genfun` as its first argument, then the `this`
|
200 | value, and then the arguments it was called with.
|
201 |
|
202 | By default, this will simply throw a NoApplicableMethod error.
|
203 |
|
204 | Users may override this behavior for particular `genfun` and `this`
|
205 | combinations, although `args` will always be an `Array`. The value returned from
|
206 | the dispatched `noApplicableMethod` method will be returned by `genfun` as if it
|
207 | had 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
|
213 | cases, is as fast as possible.
|
214 |
|
215 | How fast? Well, not much slower than native methods:
|
216 |
|
217 | ```
|
218 | Regular function: 30.402ms
|
219 | Native method: 28.109ms
|
220 | Singly-dispatched genfun: 64.467ms
|
221 | Double-dispatched genfun: 70.052ms
|
222 | Double-dispatched genfun with string primitive: 76.742ms
|
223 | ```
|