funcify 0.4.25
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +50 -0
- data/LICENSE.txt +21 -0
- data/README.md +83 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/funcify.gemspec +44 -0
- data/lib/.DS_Store +0 -0
- data/lib/funcify.rb +15 -0
- data/lib/funcify/afn.rb +274 -0
- data/lib/funcify/cond.rb +18 -0
- data/lib/funcify/fn.rb +370 -0
- data/lib/funcify/fset.rb +27 -0
- data/lib/funcify/map.rb +45 -0
- data/lib/funcify/monad.rb +53 -0
- data/lib/funcify/record.rb +31 -0
- data/lib/funcify/str.rb +15 -0
- data/lib/funcify/version.rb +3 -0
- metadata +137 -0
data/lib/funcify/cond.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Funcify
|
2
|
+
|
3
|
+
class Cond
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# The little Either Cond
|
8
|
+
# returns either the result of fn_ok || fn_fail by applying the value to test <t>.
|
9
|
+
# > either.(Monad.maybe_value_ok, identity, Monad.maybe_value).(Success(1)) => 1
|
10
|
+
def either
|
11
|
+
-> test, fn_ok, fn_fail, value { test.(value) ? fn_ok.(value) : fn_fail.(value) }.curry
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/lib/funcify/fn.rb
ADDED
@@ -0,0 +1,370 @@
|
|
1
|
+
module Funcify
|
2
|
+
|
3
|
+
class Fn
|
4
|
+
|
5
|
+
extend Dry::Monads::Try::Mixin
|
6
|
+
extend Dry::Monads::Result::Mixin
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Common curried map higher order fn
|
11
|
+
# > map.(-> i { i.to_s } ).([1,2,3])
|
12
|
+
def map
|
13
|
+
->(f, enum) { enum.map {|e| f.(e) } }.curry
|
14
|
+
end
|
15
|
+
|
16
|
+
def fmap
|
17
|
+
->(f, enum) { enum.flat_map {|e| f.(e) } }.curry
|
18
|
+
end
|
19
|
+
|
20
|
+
def sequence
|
21
|
+
->(fs, i) { fs.inject([]) { |r, f| r << f.(i) } }.curry
|
22
|
+
end
|
23
|
+
|
24
|
+
def inject
|
25
|
+
-> acc, f, xs { xs.inject(acc) {|acc, x| f.(acc).(x) } }.curry
|
26
|
+
end
|
27
|
+
|
28
|
+
def group_by
|
29
|
+
-> f, xs { xs.group_by { |x| f.(x) } }.curry
|
30
|
+
end
|
31
|
+
|
32
|
+
def merge
|
33
|
+
-> to, with { to.merge(with) }.curry
|
34
|
+
end
|
35
|
+
|
36
|
+
# Curryed fn that removes elements from a collection where f.(e) is true
|
37
|
+
def remove
|
38
|
+
->(f, enum) { enum.delete_if {|e| f.(e) } }.curry
|
39
|
+
end
|
40
|
+
|
41
|
+
# add an element to an array
|
42
|
+
def add
|
43
|
+
-> x, xs { xs << x }.curry
|
44
|
+
end
|
45
|
+
|
46
|
+
# finds the first element in a collecton where f.(e) is true
|
47
|
+
def find
|
48
|
+
->(f, enum) { enum.find { |e| f.(e) } }.curry
|
49
|
+
end
|
50
|
+
|
51
|
+
def select
|
52
|
+
->(f, enum) { enum.select { |e| f.(e) } }.curry
|
53
|
+
end
|
54
|
+
|
55
|
+
def replace
|
56
|
+
->(r, with, s) { s.gsub(r,with) }.curry
|
57
|
+
end
|
58
|
+
|
59
|
+
def join
|
60
|
+
-> sep, i { i.join(sep) }.curry
|
61
|
+
end
|
62
|
+
|
63
|
+
def split
|
64
|
+
-> sep, i { i.split(sep) }.curry
|
65
|
+
end
|
66
|
+
|
67
|
+
def max
|
68
|
+
->(f, enum) { f.(enum).max }.curry
|
69
|
+
end
|
70
|
+
|
71
|
+
def all?
|
72
|
+
->(f, enum) { enum.all? { |e| f.(e) } }.curry
|
73
|
+
end
|
74
|
+
|
75
|
+
def any?
|
76
|
+
->(f, enum) { enum.any? { |e| f.(e) } }.curry
|
77
|
+
end
|
78
|
+
|
79
|
+
def none?
|
80
|
+
->(f, enum) { enum.none? { |e| f.(e) } }.curry
|
81
|
+
end
|
82
|
+
|
83
|
+
# > uniq.(identity).([1,1,2])
|
84
|
+
def uniq
|
85
|
+
-> f, enum { enum.uniq { |e| f.(e) } }.curry
|
86
|
+
end
|
87
|
+
|
88
|
+
# > partition.(-> x { x == 1 }).([1,1,2,3,4])
|
89
|
+
def partition
|
90
|
+
-> f, enum { enum.partition { |e| f.(e) } }.curry
|
91
|
+
end
|
92
|
+
|
93
|
+
def last
|
94
|
+
-> xs { xs.last }
|
95
|
+
end
|
96
|
+
|
97
|
+
def first
|
98
|
+
-> xs { xs.first }
|
99
|
+
end
|
100
|
+
|
101
|
+
def rest
|
102
|
+
-> xs {
|
103
|
+
_a, *b = xs
|
104
|
+
b
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def when_nil?
|
109
|
+
->(i) { i.nil? }
|
110
|
+
end
|
111
|
+
|
112
|
+
def flatten
|
113
|
+
-> xs { xs.flatten }
|
114
|
+
end
|
115
|
+
|
116
|
+
# lifts the value, otherwise returns nil
|
117
|
+
def lift
|
118
|
+
->(f, with, i) { f.(i) ? with.(i) : nil }.curry
|
119
|
+
end
|
120
|
+
|
121
|
+
# Takes a structure (like a Monad), an OK test fn, and a fn to extract when OK
|
122
|
+
# Returns the result of f, otherwise nil
|
123
|
+
# > lift_value.(maybe_value_ok, maybe_value),
|
124
|
+
def lift_value
|
125
|
+
->(value_type, f) { Fn.lift.(value_type, f) }.curry
|
126
|
+
end
|
127
|
+
|
128
|
+
def lift_monad
|
129
|
+
-> value { maybe_value_ok?.(value) ? maybe_value.(value) : maybe_failure.(value) }
|
130
|
+
end
|
131
|
+
|
132
|
+
def identity
|
133
|
+
->(i) { i }
|
134
|
+
end
|
135
|
+
|
136
|
+
def method
|
137
|
+
-> m, obj { obj.send(m) }.curry
|
138
|
+
end
|
139
|
+
|
140
|
+
# The little Either Cond
|
141
|
+
# returns either the result of f_ok || f_fail by applying the value to test t.
|
142
|
+
# > either.(maybe_value_ok, identity, maybe_failure).(Success(1)) => Success(1)
|
143
|
+
def either
|
144
|
+
->(test, f_ok, f_fail, value) { test.(value) ? f_ok.(value) : f_fail.(value) }.curry
|
145
|
+
end
|
146
|
+
|
147
|
+
# success_fn: a test fn to apply to the enum resulting from applying the tests; e.g. Fn.all? (and) or Fn.any? (or)
|
148
|
+
# test_fns : [test_fn]; each test is called with (value)
|
149
|
+
# value : the test context (can be anything understood by the tests)
|
150
|
+
# > tests.(all?, [-> x { x == 1}]).(1) => true
|
151
|
+
def tests
|
152
|
+
-> success_fn, test_fns, value {
|
153
|
+
Fn.compose.(
|
154
|
+
success_fn.(Fn.identity), # provide a results extractor fn to the success_fn
|
155
|
+
Fn.map.(-> test_fn { test_fn.(value) } ), # call each test fn with the context
|
156
|
+
).(test_fns)
|
157
|
+
}.curry
|
158
|
+
end
|
159
|
+
|
160
|
+
# the famous compose
|
161
|
+
# Applies from right to left, taking the result of 1 fn and injecting into the next
|
162
|
+
# No Monads tho!
|
163
|
+
# compose.(-> n { n + 1}, -> n { n * 2 }).(10)
|
164
|
+
def compose
|
165
|
+
->(*fns) { fns.reduce { |f, g| lambda { |x| f.(g.(x)) } } }
|
166
|
+
end
|
167
|
+
|
168
|
+
# Monadic Compose, using flat_map
|
169
|
+
# The result of a fn must return an Either.
|
170
|
+
# fmap_compose.([->(v) { M.Success(v + 1) }, ->(v) { M.Success(v + 10) }]).(M.Success(0))
|
171
|
+
def fmap_compose
|
172
|
+
->(fns, value) {
|
173
|
+
fns.inject(value) {|result, fn| result.success? ? result.fmap(fn).value_or : result}
|
174
|
+
}.curry
|
175
|
+
end
|
176
|
+
|
177
|
+
# reverse version of fmap_compose
|
178
|
+
def fmapr_compose
|
179
|
+
->(*fns) {
|
180
|
+
-> x { fns.reverse.inject(x) {|x, fn| x.success? ? x.fmap(fn).value_or : x} }
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
# Apply that takes a function and an enum and applies the fn to the entire enum
|
185
|
+
# Works with methods like #join, #split
|
186
|
+
def apply
|
187
|
+
->(f, enum) { f.(enum) }.curry
|
188
|
+
end
|
189
|
+
|
190
|
+
# + field; the property to extract from the record. Either a String/Symb or a Proc which takes the record
|
191
|
+
# + test_value; the value which has == applied to determine equality
|
192
|
+
# + i; the record under test
|
193
|
+
# e.g. equality.(:a).("equal").({a: "equal"})
|
194
|
+
# e.g. equality.(test_fn).("equal").({a: "equal"})) ; where test_fn is -> x { x[:a] }
|
195
|
+
def equality
|
196
|
+
->( field, test_value, i ) {
|
197
|
+
if field.kind_of?(Proc)
|
198
|
+
field.(i) == test_value
|
199
|
+
else
|
200
|
+
i[field] == test_value
|
201
|
+
end
|
202
|
+
}.curry
|
203
|
+
end
|
204
|
+
|
205
|
+
# x can either be an array or a string
|
206
|
+
def include
|
207
|
+
-> x, v { x.include?(v) }.curry
|
208
|
+
end
|
209
|
+
|
210
|
+
# Right Include, where the value is applied partially waiting for the test prop
|
211
|
+
def rinclude
|
212
|
+
-> v, x { x.include?(v) }.curry
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
def linclusion
|
217
|
+
->( field, value, i ) { i[field].include?(value) }.curry
|
218
|
+
end
|
219
|
+
|
220
|
+
# takes a regex and applies it to a value
|
221
|
+
def match
|
222
|
+
->(r, i) { i.match(r) }.curry
|
223
|
+
end
|
224
|
+
|
225
|
+
def take
|
226
|
+
->(f, i) { f.(i) unless i.nil? }.curry
|
227
|
+
end
|
228
|
+
|
229
|
+
# right at; takes the key/index and applies the enum
|
230
|
+
def at
|
231
|
+
->(x, i) { i[x] unless i.nil? }.curry
|
232
|
+
end
|
233
|
+
|
234
|
+
# left at; takes the enum and applies the key/index to it.
|
235
|
+
def lat
|
236
|
+
->(i, x) { i[x] }.curry
|
237
|
+
end
|
238
|
+
|
239
|
+
def all_keys
|
240
|
+
-> h { h.flat_map { |k, v| [k] + (v.is_a?(Hash) ? all_keys.(v) : [v]) } }
|
241
|
+
end
|
242
|
+
|
243
|
+
def coherse
|
244
|
+
-> f, xs { map.(-> x { x.send(f) } ).(xs) }.curry
|
245
|
+
end
|
246
|
+
|
247
|
+
def max_int
|
248
|
+
-> limit, i { i > limit ? limit : i }.curry
|
249
|
+
end
|
250
|
+
|
251
|
+
def failure
|
252
|
+
-> v { Failure(v) }
|
253
|
+
end
|
254
|
+
|
255
|
+
def success
|
256
|
+
-> v { Success(v) }
|
257
|
+
end
|
258
|
+
|
259
|
+
def maybe_value_ok?
|
260
|
+
->(v) { v.respond_to?(:success?) && v.success? }
|
261
|
+
end
|
262
|
+
|
263
|
+
def maybe_value_fail?
|
264
|
+
-> v { v.respond_to?(:failure?) && v.failure? }
|
265
|
+
end
|
266
|
+
|
267
|
+
def maybe_value
|
268
|
+
->(v) { v.value_or }
|
269
|
+
end
|
270
|
+
|
271
|
+
def maybe_failure
|
272
|
+
->(v) { v.failure }
|
273
|
+
end
|
274
|
+
|
275
|
+
def status_value_ok?
|
276
|
+
->(v) { v.status == :ok }
|
277
|
+
end
|
278
|
+
|
279
|
+
def ctx_value
|
280
|
+
->(v) { v.context }
|
281
|
+
end
|
282
|
+
|
283
|
+
def method_caller
|
284
|
+
-> obj, method, v { obj.send(method, v) }.curry
|
285
|
+
end
|
286
|
+
|
287
|
+
def break_point
|
288
|
+
-> args { binding.pry }
|
289
|
+
end
|
290
|
+
|
291
|
+
def empty?
|
292
|
+
-> xs { xs.empty? }
|
293
|
+
end
|
294
|
+
|
295
|
+
def present?
|
296
|
+
-> x { x.present? }
|
297
|
+
end
|
298
|
+
|
299
|
+
def nothing
|
300
|
+
-> x { nil }
|
301
|
+
end
|
302
|
+
|
303
|
+
def remove_nil
|
304
|
+
Fn.remove.(->(i) { i.nil? } )
|
305
|
+
end
|
306
|
+
|
307
|
+
# f: add fn
|
308
|
+
# g: remove fn
|
309
|
+
# prev state
|
310
|
+
# this state
|
311
|
+
def change_set_fn
|
312
|
+
-> f, g, prev, this {
|
313
|
+
f.(Set.new(this) - Set.new(prev))
|
314
|
+
g.(Set.new(prev) - Set.new(this))
|
315
|
+
}.curry
|
316
|
+
end
|
317
|
+
|
318
|
+
# Takes a collection and generates a string delimited by the delimiter (such as "|")
|
319
|
+
# Returns a curryed fn (ready for the collection) that takes 2 params:
|
320
|
+
# @param f, a function that extracts the properties from a map; e.g. F.map.(F.identity)
|
321
|
+
# @param enum, the map
|
322
|
+
def delimiter_tokeniser
|
323
|
+
-> delimiter, f, enum { f.(enum).join(delimiter) }.curry
|
324
|
+
end
|
325
|
+
|
326
|
+
def delimiter_detokeniser
|
327
|
+
-> delimiter, f, enum { map.(f, enum).join(delimiter) }.curry
|
328
|
+
end
|
329
|
+
|
330
|
+
def detokeniser(delimiter)
|
331
|
+
->(str) { str.split(delimiter) }.curry
|
332
|
+
end
|
333
|
+
|
334
|
+
|
335
|
+
# Provides a Maybe pipeline wrapped in a Lambda. This allows the pipeline functions to be
|
336
|
+
# applied first, and returns a function which allows the injection of the params to be applied into the
|
337
|
+
# beginning of the pipeline.
|
338
|
+
# e.g.
|
339
|
+
# pipeline = maybe_pipeline.([-> x { Success(x + 1) } ] )
|
340
|
+
# pipeline.value_or.(Success(1)) => Success(2)
|
341
|
+
def maybe_pipeline
|
342
|
+
->(pipeline) {
|
343
|
+
Success(lambda do |value|
|
344
|
+
pipeline.inject(value) do |result, fn|
|
345
|
+
result.success? ? result.fmap(fn).value_or : result
|
346
|
+
end
|
347
|
+
end)
|
348
|
+
}
|
349
|
+
end
|
350
|
+
|
351
|
+
# takes a function which is ready to be executed and wraps it in a function which will finally invoke it,
|
352
|
+
# by calling with empty arguments
|
353
|
+
def wrapper
|
354
|
+
-> fn { -> { fn } }
|
355
|
+
end
|
356
|
+
|
357
|
+
def all_success?
|
358
|
+
Fn.all?.(Monad.maybe_value_ok?)
|
359
|
+
end
|
360
|
+
|
361
|
+
def hash_to_tokens
|
362
|
+
compose.(join.(","), Map.map.(-> k, v { "#{k}:#{v}"}))
|
363
|
+
end
|
364
|
+
|
365
|
+
|
366
|
+
end # class Self
|
367
|
+
|
368
|
+
end # class
|
369
|
+
|
370
|
+
end # module
|
data/lib/funcify/fset.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Funcify
|
2
|
+
|
3
|
+
class FSet
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def subset?
|
8
|
+
-> subset, superset { to_set(subset).subset?(to_set(superset)) }.curry
|
9
|
+
end
|
10
|
+
|
11
|
+
def superset?
|
12
|
+
-> superset, subset { to_set(superset).superset?(to_set(subset)) }.curry
|
13
|
+
end
|
14
|
+
|
15
|
+
def eq?
|
16
|
+
-> seta, setb { to_set(seta) == (to_set(setb)) }.curry
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_set(array_or_set)
|
20
|
+
array_or_set.instance_of?(Array) ? Set.new(array_or_set) : array_or_set
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/funcify/map.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Funcify
|
2
|
+
|
3
|
+
class Map
|
4
|
+
|
5
|
+
extend Dry::Monads::Try::Mixin
|
6
|
+
extend Dry::Monads::Result::Mixin
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def map
|
11
|
+
->(f, ms) { ms.map {|k,v| f.(k,v) } }.curry
|
12
|
+
end
|
13
|
+
|
14
|
+
def any?
|
15
|
+
->(f, ms) { ms.any? {|k,v| f.(k,v) } }.curry
|
16
|
+
end
|
17
|
+
|
18
|
+
def fmap
|
19
|
+
->(f, ms) { ms.flat_map {|k,v| f.(k,v) } }.curry
|
20
|
+
end
|
21
|
+
|
22
|
+
def inject
|
23
|
+
-> j, f, ms { ms.inject(j) {|acc, (k,v)| f.(acc).(k,v) } }.curry
|
24
|
+
end
|
25
|
+
|
26
|
+
def select
|
27
|
+
-> f, ms { ms.select {|k,v| f.(k,v) } }.curry
|
28
|
+
end
|
29
|
+
|
30
|
+
def equality
|
31
|
+
-> field, test_value, i {
|
32
|
+
if field.kind_of?(Proc)
|
33
|
+
field.(i) == test_value
|
34
|
+
else
|
35
|
+
i[field] == test_value
|
36
|
+
end
|
37
|
+
}.curry
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end # class Self
|
42
|
+
|
43
|
+
end # class
|
44
|
+
|
45
|
+
end # module
|