funcify 0.4.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|