-
Notifications
You must be signed in to change notification settings - Fork 10
/
method-combinators.coffee
128 lines (110 loc) · 3.6 KB
/
method-combinators.coffee
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
# # Method Combinators
#
# Source: https://github.com/raganwald/method-combinators
#
# ## The four basic combinators
this.before =
(decoration) ->
(base) ->
->
decoration.apply(this, arguments)
base.apply(this, arguments)
this.after =
(decoration) ->
(base) ->
->
decoration.call(this, __value__ = base.apply(this, arguments))
__value__
this.around =
(decoration) ->
(base) ->
(argv...) ->
__value__ = undefined
apply_base = =>
__value__ = base.apply(this, argv)
decoration.apply(this, [apply_base].concat(argv))
__value__
this.provided =
(predicate) ->
(base) ->
->
if predicate.apply(this, arguments)
base.apply(this, arguments)
this.excepting =
(predicate) ->
(base) ->
->
unless predicate.apply(this, arguments)
base.apply(this, arguments)
# ## Extras
# If the method throws an error, retry it again a certain number of times.
# e.g. `retry(3) -> # doSomething as many as four times`
this.retry =
(times) ->
(base) ->
->
return unless times >= 0
loop
try
return base.apply(this, arguments)
catch error
throw error unless (times -= 1) >= 0
# Throw an error before the method is executed if the prepredicate function fails, with an
# optional message, e.g. `prepredicate 'account must be valid', -> @account.isValid()` or
# `prepredicate -> @account.isValid()`
this.precondition =
(throwable, predicate) ->
(predicate = throwable) and (throwable = 'Failed precondition') unless predicate
this.before -> throw throwable unless predicate.apply(this, arguments)
# Throw an error after the method is executed if the postpredicate function fails, with an
# optional message, e.g. `postpredicate 'account must be valid', -> @account.isValid()` or
# `postpredicate -> @account.isValid()`
this.postcondition =
(throwable, predicate) ->
(predicate = throwable) and (throwable = 'Failed postcondition') unless predicate
this.after -> throw throwable unless predicate.apply(this, arguments)
# Apply the method to each member of an array
this.splat = (base) ->
->
for arg0 in arguments[0]
argv = [].slice.call(arguments, 0)
argv[0] = arg0
base.apply this, argv
# Run function on each member of array, equivalent to map
this.splatter =
(base)->
(args...)->
array = args[0]
for element in array
newArgs = args.slice(0)
newArgs[0] = element
base.apply this, newArgs
# ## Asynchronous Method Combinators
this.async = do (async = undefined) ->
async = (fn) ->
(argv..., callback) ->
callback(fn.apply(this, argv))
async.before = (async_decoration) ->
(async_base) ->
(argv..., callback) ->
__value__ = undefined
apply_base = =>
__value__ = async_base.apply(this, argv.concat(callback))
async_decoration.apply(this, argv.concat(apply_base))
__value__
async.after = (async_decoration) ->
(async_base) ->
(argv..., callback) ->
decorated_callback = (callback_argv...) =>
async_decoration.apply(this, callback_argv.concat(-> callback.apply(this, callback_argv)))
async_base.apply(this, argv.concat(decorated_callback))
async.provided = (async_predicate) ->
(async_base) ->
(argv..., callback) ->
decorated_base = (predicate_value) ->
if predicate_value
async_base.apply(this, argv.concat(callback))
else
callback()
async_predicate.apply(this, argv.concat(decorated_base))
async