-
Notifications
You must be signed in to change notification settings - Fork 8
/
multimethod.js
181 lines (156 loc) · 6.53 KB
/
multimethod.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// multimethod.js 0.1.0
//
// (c) 2011 Kris Jordan
//
// `multimethod` is freely distributable under the MIT license.
// For details and documentation:
// [http://krisjordan.com/multimethod-js](http://krisjordan.com/multimethod-js)
(function() {
// Multimethods are a functional programming control structure for dispatching
// function calls with user-defined criteria that can be changed at run time.
// Inspired by clojure's multimethods, multimethod.js provides an alternative to
// classical, prototype-chain based polymorphism.
// ## Internal Utility Functions
// No operation function used by default by `default`.
var noop = function() {};
// Identity `dispatch` function. Default value of `dispatch`.
var identity = function(a) { return a; };
// A `method` in `multimethod` is a (match value, function) pair stored in
// an array. `indexOf` takes a value and array of methods and returns the
// index of the method whose value is equal to the first argument. If no
// match is found, false is returned.
var indexOf = function(value, methods) {
for(var i in methods) {
var matches = methods[i][0];
if(_(value).isEqual(matches)) {
return i;
}
}
return false;
}
// Given a dispatch `value` and array of `method`s, return the function
// of the `method` whose match value corresponds to a dispatch value.
var match = function(value, methods) {
var index = indexOf(value, methods);
if(index !== false) {
return methods[index][1];
} else {
return false;
}
}
// Simple, consistent helper that returns a native value or invokes a function
// and returns its return value. Used by `when` and `default` allowing
// short-hand notation for returning values rather than calling functions.
var toValue = function(subject, args) {
if(_.isFunction(subject)) {
return subject.apply(this, args);
} else {
return subject;
}
};
// Plucking a single property value from an object in `dispatch` is commonly
// used. The internal `pluck` function returns a function suitable for use
// by `dispatch` for just that purpose.
var pluck = function(property) {
return function(object) {
return object[property];
}
};
// ## Implementation
// `multimethod` is a higher-order function that returns a closure with
// methods to control its behavior.
var multimethod = function(dispatch) {
// ### Private Properties
// `_dispatch` holds either a dispatch function or a string
// corresponding to the property name whose value will be plucked
// and used as the `dispatch` criteria.
var _dispatch,
// `_methods` is a an array of `method` arrays. A `method` is
// [ matchValue, implementation ].
_methods = [],
// `_default` is the fallback method when a `multimethod` is called
// and matches no other method.
_default = noop;
// The fundamental control flow of the `multimethod` is implemented
// in `_lookup`. First we invoke the dispatch function, this gives
// us our match criteria. Then we match a method based on the criteria
// or return the default method.
var _lookup = function() {
var criteria = _dispatch.apply(this, arguments),
method = match(criteria, _methods);
if(method !== false) {
return method;
} else {
return _default;
}
};
// The result of calling `multimethod`'s "factory" function is this function.
var returnFn = function() {
var method = _lookup.apply(this, arguments);
return toValue.call(this, method, arguments);
};
// ### Member Methods / API
// `dispatch` is the accessor to the `multimethod`'s `_dispatch` function.
// When called with a string we create an anonymous pluck function as a
// shorthand.
returnFn['dispatch'] = function(dispatch) {
if(_.isFunction(dispatch)) {
_dispatch = dispatch;
} else if(_.isString(dispatch)) {
_dispatch = pluck(dispatch);
} else {
throw "dispatch requires a function or a string.";
}
return this;
}
// If `multimethod` is called/"constructed" with a `dispatch` value we go ahead and set
// it up here. Otherwise `dispatch` is the `identity` function.
returnFn.dispatch(dispatch || identity);
// `when` introduces new `method`s to a `multimethod`. If the
// `matchValue` has already been registered the new method will
// overwrite the old method.
returnFn['when'] = function(matchValue, fn) {
var index = indexOf(matchValue, _methods);
if(index !== false) {
_methods[index] = [matchValue, fn];
} else {
_methods.push([matchValue, fn]);
}
return this;
}
// `remove` will unregister a `method` based on matchValue
returnFn['remove'] = function(matchValue) {
var index = indexOf(matchValue, _methods);
if(index !== false) {
_methods.splice(index, 1);
}
return this;
}
// `default` is an accessor to control the `_default`, fallback method
// that is called when no match is found when the `multimethod` is
// invoked and dispatched.
returnFn['default'] = function(method) {
_default = method;
return this;
}
// Our `multimethod` instance/closure is fully setup now, return!
return returnFn;
};
// The following snippet courtesy of underscore.js.
// Export `multimethod` to the window/exports namespace.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = multimethod;
var _ = require('underscore');
}
exports.multimethod = multimethod;
} else if (typeof define === 'function' && define.amd) {
define('multimethod', function() {
return multimethod;
});
} else {
this['multimethod'] = multimethod;
var _ = this['_'];
}
multimethod.version = '0.1.0';
}).call(this);