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``` 224