feldspar 0.2.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9231039dc87d5d92019e68daae54d25a33b55d4f
4
+ data.tar.gz: 63dc198359931c4a147e1195006a9f7fd00cdcd3
5
+ SHA512:
6
+ metadata.gz: 93e7050ca8e12c84cce36c406273cda60ac83fa4f2d23dcfeb25fdfebf880b738a948efa24046e3ad3a07b69fbde2a9803eb201a777b906c8f1c56526e7d8da0
7
+ data.tar.gz: bd1ba6210612ebd42433afc64b35e28c4b0cbaf373523ccde06fe3856b9ba16c875f84b54fc9d8abaf013482df2267993c23ad21d2bcbb6d9b5252ce38802e9c
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ .gitconfig_local
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.3
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in feldspar.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ feldspar (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ rake (10.5.0)
11
+ rspec (3.7.0)
12
+ rspec-core (~> 3.7.0)
13
+ rspec-expectations (~> 3.7.0)
14
+ rspec-mocks (~> 3.7.0)
15
+ rspec-core (3.7.1)
16
+ rspec-support (~> 3.7.0)
17
+ rspec-expectations (3.7.0)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.7.0)
20
+ rspec-mocks (3.7.0)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.7.0)
23
+ rspec-support (3.7.1)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ bundler (~> 1.16)
30
+ feldspar!
31
+ rake (~> 10.0)
32
+ rspec (~> 3.0)
33
+
34
+ BUNDLED WITH
35
+ 1.16.1
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # Feldspar
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/feldspar`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'feldspar'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install feldspar
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/feldspar.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "feldspar"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/feldspar.gemspec ADDED
@@ -0,0 +1,26 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "feldspar/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "feldspar"
8
+ spec.version = Feldspar::VERSION
9
+ spec.authors = ["Conner Bryan"]
10
+ spec.email = ["conner@amps.io"]
11
+
12
+ spec.summary = %q{functional ruby}
13
+ # spec.description = %q{TODO: Write a longer description or delete this line.}
14
+ # spec.homepage = "TODO: Put your gem's website or public repo URL here."
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.16"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.0"
26
+ end
data/lib/feldspar.rb ADDED
@@ -0,0 +1,18 @@
1
+ require "feldspar/add"
2
+ require "feldspar/always"
3
+ require "feldspar/add_index"
4
+ require "feldspar/adjust"
5
+ require "feldspar/all"
6
+ require "feldspar/all_pass"
7
+ require "feldspar/curry"
8
+ require "feldspar/identity"
9
+ require "feldspar/filter"
10
+ require "feldspar/map"
11
+ require "feldspar/version"
12
+ require "feldspar/splitme"
13
+
14
+
15
+ module Feldspar
16
+ end
17
+
18
+ F = Feldspar
@@ -0,0 +1,8 @@
1
+ require "feldspar/curry"
2
+
3
+ module Feldspar
4
+ curry_functions
5
+ def add(x, y)
6
+ x + y
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ require "feldspar/curry"
2
+
3
+ module Feldspar
4
+ curry_functions
5
+ def add_index(iteration_fn)
6
+ curry(-> (fn, list) {
7
+ i = 0
8
+ iteration_fn.(-> (x) {
9
+ r = fn.(x, i)
10
+ i = i + 1
11
+ r
12
+ }, list)
13
+ })
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ require "feldspar/curry"
2
+
3
+ module Feldspar
4
+ curry_functions
5
+ def adjust(fn, index, list)
6
+ copy = Array.new(list)
7
+ copy[index] = fn.(copy[index])
8
+ copy
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ require "feldspar/curry"
2
+
3
+ module Feldspar
4
+ curry_functions
5
+ def all?(fn, list)
6
+ list.all? { |x| fn.(x) }
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ require "feldspar/curry"
2
+
3
+ module Feldspar
4
+ curry_functions
5
+ def all_pass(fns)
6
+ curry(-> (x) {
7
+ fns.reduce(true) { |memo, fn|
8
+ memo && fn.(x)
9
+ }
10
+ })
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ require "feldspar/curry"
2
+
3
+ module Feldspar
4
+ curry_functions
5
+ def always(x)
6
+ -> *args { x }
7
+ end
8
+ end
@@ -0,0 +1,91 @@
1
+ module Feldspar
2
+ module_function
3
+ def curry(fn)
4
+ # TODO: handle varargs after fixed args: (*a, b) or (a, *b, c)
5
+ if fn.arity == -1
6
+ fn
7
+ elsif fn.arity > 0
8
+ curry_n(fn.arity, fn)
9
+ else
10
+ fn.curry
11
+ end
12
+ end
13
+
14
+ def curry_n(n, fn)
15
+ if n == -1
16
+ -> *args { fn.(*args) }
17
+ else
18
+ _curry_n(n, [], fn)
19
+ end
20
+ end
21
+
22
+ def _curry_n(n, received, fn)
23
+ -> *args {
24
+ combined = []
25
+ args_index = 0
26
+ left = n
27
+ combined_index = 0
28
+ while(combined_index < received.length || args_index < args.length)
29
+ result = if combined_index < received.length && (!is_placeholder(received[combined_index]) || args_index >= args.length)
30
+ result = received[combined_index]
31
+ else
32
+ result_arg_index = args_index
33
+ args_index = args_index + 1
34
+ result = args[result_arg_index]
35
+ end
36
+ combined[combined_index] = result
37
+ if !is_placeholder(result)
38
+ left = left - 1
39
+ end
40
+ combined_index = combined_index + 1
41
+ end
42
+ if left <= 0
43
+ fn.call(*combined)
44
+ else
45
+ _curry_n(n, combined, fn)
46
+ # _arity(left, )
47
+ end
48
+ }
49
+ end
50
+
51
+ def _arity(n, fn)
52
+ case n
53
+ when 0; -> { fn.() }
54
+ when 1; -> a { fn.(a) }
55
+ when 2; -> a, b { fn.(a, b) }
56
+ when 3; -> a, b, c { fn.(a, b, c) }
57
+ when 4; -> a, b, c, d { fn.(a, b, c, d) }
58
+ when 5; -> a, b, c, d, e { fn.(a, b, c, d, e) }
59
+ when 6; -> a, b, c, d, e, f { fn.(a, b, c, d, e, f) }
60
+ when 7; -> a, b, c, d, e, f, g { fn.(a, b, c, d, e, f, g) }
61
+ when 8; -> a, b, c, d, e, f, g, h { fn.(a, b, c, d, e, f, g, h) }
62
+ when 9; -> a, b, c, d, e, f, g, h, i { fn.(a, b, c, d, e, f, g, h, i) }
63
+ when 10; -> a, b, c, d, e, f, g, h, i, j { fn.(a, b, c, d, e, f, g, h, i, j) }
64
+ else; raise "Cannot create function arity > 10."
65
+ end
66
+ end
67
+
68
+ def is_placeholder(x)
69
+ x == :__feldspar__underscore
70
+ end
71
+
72
+ def curry_inject(target, meth)
73
+ target.instance_eval do
74
+ method_object = instance_method(meth)
75
+ define_singleton_method(meth, curry(method_object.bind(self)))
76
+ end
77
+ end
78
+
79
+
80
+ def curry_functions
81
+ @curry_calls_internal = false
82
+ def self.method_added(meth)
83
+ unless @curry_calls_internal
84
+ @curry_calls_internal = true
85
+ Feldspar::curry_inject(self, meth)
86
+ @curry_calls_internal = false
87
+ end
88
+ end
89
+ end
90
+
91
+ end
@@ -0,0 +1,8 @@
1
+ require "feldspar/curry"
2
+
3
+ module Feldspar
4
+ curry_functions
5
+ def filter(fn, list)
6
+ list.select { |x| fn.(x) }
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require "feldspar/curry"
2
+
3
+ module Feldspar
4
+ curry_functions
5
+ def identity(x)
6
+ x
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ require "feldspar/curry"
2
+
3
+ module Feldspar
4
+ curry_functions
5
+ def map(fn, list)
6
+ if list.is_a?(Array)
7
+ list.map { |x| fn.(x) }
8
+ elsif list.is_a?(Hash)
9
+ list.map { |k, v| [k, fn.(v)] }.to_h
10
+ elsif list.respond_to?(:map)
11
+ list.map { |x| fn.(x) }
12
+ else
13
+ nil
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ require "feldspar/curry"
2
+
3
+ module Feldspar
4
+ curry_functions
5
+ def reject(fn, list)
6
+ list.reject { |x| fn.(x) }
7
+ end
8
+ end
@@ -0,0 +1,1486 @@
1
+ require "feldspar/curry"
2
+ require "set"
3
+
4
+ module Feldspar
5
+
6
+ curry_functions
7
+
8
+ def and(x, y)
9
+ x && y
10
+ end
11
+
12
+ def __
13
+ :__feldspar__underscore
14
+ end
15
+
16
+ def abs(x)
17
+ x.abs
18
+ end
19
+
20
+ def any?(predicate, list)
21
+ list.any? { |x| predicate.(x) }
22
+ end
23
+
24
+ def any_pass(fns)
25
+ curry(-> (x) {
26
+ fns.reduce(false) { |memo, fn|
27
+ memo || fn(x)
28
+ }
29
+ })
30
+ end
31
+
32
+ def ap(fns, list)
33
+ reduce(-> (memo, item) {
34
+ memo + fns.map { |fn| fn.(item) }
35
+ }, [], list)
36
+ end
37
+
38
+ def aperture(size, list)
39
+ list.each_slice(size).to_a
40
+ end
41
+
42
+ def append(item, list)
43
+ clone(list).push(item)
44
+ end
45
+
46
+ def apply(fn, list)
47
+ fn.call(*list)
48
+ end
49
+
50
+ def apply_spec(spec)
51
+ curry(-> *args {
52
+ spec.map { |k, fn|
53
+ v = if fn.is_a?(Hash)
54
+ apply_spec.(fn).(*args)
55
+ else
56
+ apply(fn, args)
57
+ end
58
+ [k, v]
59
+ }.to_h
60
+ })
61
+ end
62
+
63
+ def apply_to(value, fn)
64
+ fn.(value)
65
+ end
66
+
67
+ def thrush(value, fn)
68
+ apply_to.(value, fn)
69
+ end
70
+
71
+ def ascend(ord_fn, x, y)
72
+ ax = ord_fn.(x)
73
+ bx = ord_fn.(y)
74
+ if ax < bx
75
+ -1
76
+ elsif ax > bx
77
+ 1
78
+ else
79
+ 0
80
+ end
81
+ end
82
+
83
+ def assoc(key, value, hash)
84
+ copy = clone(hash)
85
+ copy[key] = value
86
+ copy
87
+ end
88
+
89
+ def assoc_path(path, value, hash)
90
+ copy = clone(hash)
91
+ nested = path[0, path.length - 1].reduce(copy) { |memo, n|
92
+ memo[n]
93
+ }
94
+ nested[path[-1]] = value
95
+ copy
96
+ end
97
+
98
+ def binary(fn)
99
+ n_ary(2, fn)
100
+ end
101
+
102
+ def bind()
103
+ raise "TODO" # even possible?
104
+ end
105
+
106
+ def both?(xfn, yfn, value)
107
+ xfn.(value) && yfn.(value)
108
+ end
109
+
110
+ def call(fn, *args)
111
+ apply.(fn, args)
112
+ end
113
+
114
+ def chain(fn, list)
115
+ flat_map.(fn, list)
116
+ end
117
+
118
+ def clamp(min, max, value)
119
+ if value < min
120
+ min
121
+ elsif value > max
122
+ max
123
+ else
124
+ value
125
+ end
126
+ end
127
+
128
+ def clone(value)
129
+ if value.is_a?(Hash)
130
+ value.map { |k, v| [clone.(k), clone.(v)] }.to_h
131
+ elsif value.is_a?(Array)
132
+ value.map { |v| clone(v) }.to_a
133
+ elsif value.respond_to?(:clone)
134
+ value.clone
135
+ else
136
+ value
137
+ end
138
+ end
139
+
140
+ def compact(list)
141
+ if list.respond_to?(:compact)
142
+ list.compact
143
+ else
144
+ filter(is_nil?, list)
145
+ end
146
+ end
147
+
148
+ def comparator(predicate)
149
+ curry(-> a, b {
150
+ if predicate.(a, b)
151
+ -1
152
+ elsif predicate.(b, a)
153
+ 1
154
+ else
155
+ 0
156
+ end
157
+ })
158
+ end
159
+
160
+ def complement(fn)
161
+ curry(-> x {
162
+ !(fn.(x))
163
+ })
164
+ end
165
+
166
+ def compose(*fns, fn)
167
+ curry_n(fn.arity, -> *args {
168
+ seed = fn.(*args)
169
+ fns.reverse.reduce(seed) { |memo, f|
170
+ f.(memo)
171
+ }
172
+ })
173
+ end
174
+
175
+ def compose_k(*fns, fn)
176
+ wrapped = map(flat_map, fns)
177
+ compose(*wrapped, fn)
178
+ end
179
+
180
+ def compose_p(*fns)
181
+ raise "TODO"
182
+ end
183
+
184
+ def concat(list_x, list_y)
185
+ # `.concat` covers `Array` and `String`
186
+ if list_x.respond_to?(:concat)
187
+ list_x.concat(list_y)
188
+ elsif list_x.is_a?(Hash)
189
+ list_x.merge(list_y)
190
+ else
191
+ raise "Cannot concat #{list_x.class} and #{list_y.class}. Consider adding a `concat` method to #{list_x.class}."
192
+ end
193
+ end
194
+
195
+ def cond(logic_pairs)
196
+ curry(-> x {
197
+ pair = logic_pairs.find { |k, v| k.(x) }
198
+ if pair.nil?
199
+ nil
200
+ else
201
+ pair.last.(x)
202
+ end
203
+ })
204
+ end
205
+
206
+ def construct(cons)
207
+ if cons.respond_to?(:new)
208
+ meth = cons.method(:new)
209
+ curry_n(meth.arity, meth)
210
+ else
211
+ raise "Invalid constructor (`#{cons}`). Constructor should respond to `.new`."
212
+ end
213
+ end
214
+
215
+ def construct_n(n, cons)
216
+ if cons.respond_to?(:new)
217
+ meth = cons.method(:new)
218
+ curry_n(n, meth)
219
+ else
220
+ raise "Invalid constructor (`#{cons}`). Constructor should respond to `.new`."
221
+ end
222
+ end
223
+
224
+ def contains?(value, list)
225
+ include?(value, list)
226
+ end
227
+
228
+ def include?(value, list)
229
+ list.include?(value)
230
+ end
231
+
232
+ def converge(fn, branch_fns)
233
+ -> *args {
234
+ branched = ap(branch_fns, args)
235
+ fn.(*branched)
236
+ }
237
+ end
238
+
239
+ def count_by(fn, list)
240
+ compose(map.(length), group_by(fn)).(list)
241
+ end
242
+
243
+ def dec(x)
244
+ x - 1
245
+ end
246
+
247
+ def default_to(default, x)
248
+ if is_nil_like?(x) then default else x end
249
+ end
250
+
251
+ def descend(ord_fn, x, y)
252
+ ax = ord_fn.(x)
253
+ bx = ord_fn.(y)
254
+ if ax > bx
255
+ -1
256
+ elsif ax < bx
257
+ 1
258
+ else
259
+ 0
260
+ end
261
+ end
262
+
263
+ def difference(listx, listy)
264
+ if listx.is_a?(Array)
265
+ Set.new(listx - listy)
266
+ else
267
+ raise "Cannot find difference for #{listx.class}."
268
+ end
269
+ end
270
+
271
+ def difference_with(cmp, listx, listy)
272
+ listy_mapping = listy.map { |x| cmp.(x) }
273
+ Set.new(listx.select { |x|
274
+ mapping = cmp.(x)
275
+ !listy_mapping.include?(mapping)
276
+ })
277
+ end
278
+
279
+ def dissoc(key, hash)
280
+ copy = clone(hash)
281
+ copy.delete(key)
282
+ copy
283
+ end
284
+
285
+ def div(x, y)
286
+ x / y
287
+ end
288
+
289
+ def drop(n, list)
290
+ list.drop(n)
291
+ end
292
+
293
+ def drop_last(n, list)
294
+ compose(reverse, drop(n), reverse).(list)
295
+ end
296
+
297
+ def drop_last_while(fn, list)
298
+ compose(reverse, drop_while(fn), reverse).(list)
299
+ end
300
+
301
+ def drop_repeats(list)
302
+ add_index(filter).(-> x, i {
303
+ if i == 0 then true else list[i - 1] != x end
304
+ }).(list)
305
+ end
306
+
307
+ def drop_repeats_with(fn, list)
308
+ mapping = list.map { |x| fn.(x) }
309
+ add_index(filter).(-> x, i {
310
+ if i == 0 then true else mapping[i] != mapping[i - 1] end
311
+ }).(list)
312
+ end
313
+
314
+ def drop_while(fn, list)
315
+ list.drop_while { |x| fn.(x) }
316
+ end
317
+
318
+ def either(fn_a, fn_b, x)
319
+ fn_a.(x) || fn_b.(x)
320
+ end
321
+
322
+ def empty(x)
323
+ if value.is_a?(Hash)
324
+ {}
325
+ elsif value.is_a?(Array)
326
+ []
327
+ elsif value.is_a?(String)
328
+ ""
329
+ elsif value.respond_to?(:empty)
330
+ value.empty
331
+ else
332
+ value
333
+ end
334
+ end
335
+
336
+ def ends_with?(x, list)
337
+ if list.is_a?(String)
338
+ list[negate(x.length), x.length] == x
339
+ elsif list.is_a?(Array)
340
+ list[negate(x.length), x.length] == x
341
+ else
342
+ raise "Cannot check `ends_with?` for #{list.class}"
343
+ end
344
+ end
345
+
346
+ def eq_by?(fn, x, y)
347
+ fn.(x) == fn.(y)
348
+ end
349
+
350
+ def eq_props?(key, x, y)
351
+ x[key] == y[key]
352
+ end
353
+
354
+ def equal?(x, y)
355
+ if x.respond_to?(:equal?) && y.respond_to?(:equal?)
356
+ x.equal?(y) && y.equal?(x)
357
+ elsif x.respond_to?(:equal?)
358
+ x.equal?(y)
359
+ elsif y.respond_to?(:equal?)
360
+ y.equal?(x)
361
+ else
362
+ x == y
363
+ end
364
+ end
365
+
366
+ def evolve(transformations, hash)
367
+ hash.map { |k, v|
368
+ transformed = if transformations.has_key?(k)
369
+ if v.is_a?(Hash)
370
+ evolve(transformations[k], v)
371
+ else
372
+ transformations[k].(v)
373
+ end
374
+ else
375
+ v
376
+ end
377
+ [k, transformed]
378
+ }.to_h
379
+ end
380
+
381
+ def f(*args)
382
+ false
383
+ end
384
+
385
+ def filter(fn, list)
386
+ if list.is_a?(Array)
387
+ list.select { |x| fn.(x) }
388
+ elsif list.is_a?(Hash)
389
+ list.select { |k, v| [k, fn.(v)] }.to_h
390
+ else
391
+ nil
392
+ end
393
+ end
394
+
395
+ def find(fn, list)
396
+ if list.respond_to?(:find)
397
+ list.find { |x| fn.(x) }
398
+ else
399
+ raise "Cannot `find` on #{list.class}"
400
+ end
401
+ end
402
+
403
+ def find_index(fn, list)
404
+ if list.respond_to?(:find_index)
405
+ list.find_index { |x| fn.(x) }
406
+ else
407
+ raise "Cannot `find_index` on #{list.class}"
408
+ end
409
+ end
410
+
411
+ def find_last(fn, list)
412
+ rev = reverse(list)
413
+ if rev.respond_to?(:find)
414
+ rev.find { |x| fn.(x) }
415
+ else
416
+ raise "Cannot `find_last` on #{list.class}"
417
+ end
418
+ end
419
+
420
+ def find_last_index(fn, list)
421
+ rev = reverse(list)
422
+ if list.respond_to?(:find_index)
423
+ index = rev.find_index { |x| fn.(x) }
424
+ list.length - index - 1
425
+ else
426
+ raise "Cannot `find_last_index` on #{list.class}"
427
+ end
428
+ end
429
+
430
+ # alias
431
+ def select(fn, list)
432
+ filter(fn, list)
433
+ end
434
+
435
+ def flat_map(fn, list)
436
+ compose(flatten, map(fn)).(list)
437
+ end
438
+
439
+ def flatten(list)
440
+ list.flatten(1)
441
+ end
442
+
443
+ def flip(fn)
444
+ curry_n(fn.arity, -> a, b, *c {
445
+ args = [b, a] + c
446
+ apply(fn, args)
447
+ })
448
+ end
449
+
450
+ def each(fn, list)
451
+ if list.respond_to?(:each)
452
+ list.each { |x| fn.(x) }
453
+ else
454
+ raise "Cannot `each` on #{list.class}"
455
+ end
456
+ end
457
+
458
+ def from_pairs(pairs)
459
+ pairs.reduce({}) { |memo, x|
460
+ memo[x[0]] = x[1]
461
+ memo
462
+ }
463
+ end
464
+
465
+ def group_by(fn, list)
466
+ list.reduce({}) { |memo, x|
467
+ mapping = fn.(x)
468
+ map = if memo.has_key?(mapping) then memo[mapping] else [] end
469
+ map.push(x)
470
+ memo[mapping] = map
471
+ memo
472
+ }
473
+ end
474
+
475
+ def group_with(fn, list)
476
+ group_by(fn, list).values
477
+ end
478
+
479
+ def >(x, y)
480
+ x > y
481
+ end
482
+
483
+ def >=(x, y)
484
+ x >= y
485
+ end
486
+
487
+ def <(x, y)
488
+ x < y
489
+ end
490
+
491
+ def <=(x, y)
492
+ x <= y
493
+ end
494
+
495
+ def has?(key, x)
496
+ x.has_key?(key)
497
+ end
498
+
499
+ def head(list)
500
+ list[0]
501
+ end
502
+
503
+ def first(list)
504
+ head(list)
505
+ end
506
+
507
+ def identical?(x, y)
508
+ object_id(x) == object_id(y)
509
+ end
510
+
511
+ def if_else(cond, true_fn, false_fn, x)
512
+ if cond.(x)
513
+ true_fn.(x)
514
+ else
515
+ false_fn.(x)
516
+ end
517
+ end
518
+
519
+ def inc(x)
520
+ x + 1
521
+ end
522
+
523
+ def index_by(fn, list)
524
+ compose(
525
+ map(last),
526
+ group_by(fn)
527
+ ).(list)
528
+ end
529
+
530
+ def init(list)
531
+ if list.length == 0
532
+ []
533
+ else
534
+ list[0, list.length - 1]
535
+ end
536
+ end
537
+
538
+ def inner_join(pred, xs, ys)
539
+ filter(-> x {
540
+ contains_with?(pred, x, ys)
541
+ }, xs)
542
+ end
543
+
544
+ def contains_with?(pred, x, list)
545
+ index = 0
546
+ l = list.length
547
+ while index < l
548
+ if pred.(x, list[index])
549
+ return true
550
+ end
551
+ index = index + 1
552
+ end
553
+ false
554
+ end
555
+
556
+ def insert(index, x, list)
557
+ list.insert(index, x)
558
+ end
559
+
560
+ def insert_all(index, xs, list)
561
+ list.insert(index, *xs)
562
+ end
563
+
564
+ def intersection(x, y)
565
+ Set.new(x & y)
566
+ end
567
+
568
+ def &(x, y)
569
+ intersection(x, y)
570
+ end
571
+
572
+ def intersperse(x, xs)
573
+ compose(flatten, map_indexed(-> y, i {
574
+ if i > 0 then [x, y] else [y] end
575
+ })).(xs)
576
+ end
577
+
578
+ def into(acc, xf, list)
579
+ raise "TODO"
580
+ end
581
+
582
+ def invert(x)
583
+ found = {}
584
+ invert_obj_by(-> key, existing, value {
585
+ if found.has_key?(key)
586
+ state = found[key]
587
+ base = if state == 1
588
+ base = [existing]
589
+ elsif state == 2
590
+ base = existing
591
+ end
592
+ found[key] = 2
593
+ base.push(value)
594
+ base
595
+ else
596
+ found[key] = 1
597
+ value
598
+ end
599
+ }, x)
600
+ end
601
+
602
+ def invert_obj(x)
603
+ invert_obj_by(-> key, existing, value {
604
+ value
605
+ }, x)
606
+ end
607
+
608
+ def invert_obj_by(fn, x)
609
+ if x.is_a?(String)
610
+ invert_obj_by(fn, x.split(""))
611
+ elsif x.is_a?(Array)
612
+ invert_obj_by(fn, Hash[(0...x.size).zip x])
613
+ elsif x.is_a?(Hash)
614
+ x.keys.reduce({}) { |memo, y|
615
+ original_value = x[y]
616
+ existing_value = if memo.has_key?(original_value)
617
+ memo[original_value]
618
+ else
619
+ nil
620
+ end
621
+ value = fn.(original_value, existing_value, y)
622
+ memo[original_value] = value
623
+ memo
624
+ }
625
+ else
626
+ raise "Cannot invert a `#{x.class}`"
627
+ end
628
+ end
629
+
630
+ def invoker(arity, meth)
631
+ curry_n(arity + 1, -> *args, target {
632
+ target.public_send(meth, *args)
633
+ })
634
+ end
635
+
636
+ def is_a?(t, x)
637
+ x.is_a?(t)
638
+ end
639
+
640
+ def is_empty?(x)
641
+ if x.is_a?(Array)
642
+ x.length == 0
643
+ elsif x.is_a?(String)
644
+ x.length == 0
645
+ elsif x.is_a?(Hash)
646
+ x.length == 0
647
+ else
648
+ x.nil?
649
+ end
650
+ end
651
+
652
+ def is_nil_like?(x)
653
+ is_nil?(x) || is_nan?(x)
654
+ end
655
+
656
+ def is_nil?(x)
657
+ x.nil?
658
+ end
659
+
660
+ def is_nan?(x)
661
+ x.is_a?(Float) && x.nan?
662
+ end
663
+
664
+ def join(separator, list)
665
+ list.join(separator)
666
+ end
667
+
668
+ def juxt(fns)
669
+ -> *args {
670
+ fns.map { |fn|
671
+ apply(fn, args)
672
+ }
673
+ }
674
+ end
675
+
676
+ def keys(x)
677
+ x.keys
678
+ end
679
+
680
+ def last(list)
681
+ list[-1]
682
+ end
683
+
684
+ def last_index_of(x, list)
685
+ find_last_index(equal?(x), list)
686
+ end
687
+
688
+ def length(x)
689
+ x.length
690
+ end
691
+
692
+ Lens = Struct.new(:getter, :setter)
693
+
694
+ def lens(getter, setter)
695
+ Lens.new(getter, setter)
696
+ end
697
+
698
+ def lens_index(index)
699
+ lens(nth(index), update(index))
700
+ end
701
+
702
+ def lens_path(p)
703
+ lens(path(p), assoc_path(p))
704
+ end
705
+
706
+ def lens_prop(key)
707
+ lens(prop(key), assoc(key))
708
+ end
709
+
710
+ def lift(fn)
711
+ lift_n(fn.arity, fn)
712
+ end
713
+
714
+ def lift_n(arity, fn)
715
+ lifted = curry_n(arity, fn)
716
+ curry_n(arity, -> arg, *args {
717
+ reduce(ap, map(lifted, arg), args)
718
+ })
719
+ end
720
+
721
+ def map_indexed(fn, xs)
722
+ add_index(map).(fn, xs)
723
+ end
724
+
725
+ def map_accum(fn, acc, list)
726
+ memo = acc
727
+ result = map(-> (x) {
728
+ memo, r = fn.(memo, x)
729
+ r
730
+ }, list)
731
+ [memo, result]
732
+ end
733
+
734
+ def map_accum_right(fn, acc, list)
735
+ result = []
736
+ index = list.length - 1
737
+ tuple = [acc]
738
+ while index >= 0
739
+ tuple = fn.(list[index], tuple[0])
740
+ result[index] = tuple[1]
741
+ index = index - 1
742
+ end
743
+ [result, tuple[0]]
744
+ end
745
+
746
+ def map_hash_indexed(fn, hash)
747
+ hash.reduce({}) { |memo, x|
748
+ k, v = x
749
+ memo[k] = fn.(v, k, hash)
750
+ memo
751
+ }
752
+ end
753
+
754
+ def match(pattern, x)
755
+ if x.is_a?(String)
756
+ x.scan(pattern).map { |m| m[0] }
757
+ else
758
+ pattern.match(x).to_a
759
+ end
760
+ end
761
+
762
+ def %(x, y)
763
+ x % y
764
+ end
765
+
766
+ def max(x, y)
767
+ if x > y then x else y end
768
+ end
769
+
770
+ def max_by(fn, list)
771
+ list.map { |x| [fn.(x), x] }
772
+ .to_h
773
+ .sort
774
+ .last[1]
775
+ end
776
+
777
+ def mean(list)
778
+ sum(list) / list.length
779
+ end
780
+
781
+ def median(list)
782
+ sorted = list.sort
783
+ half = (list.length / 2).round
784
+ sorted[half]
785
+ end
786
+
787
+ def memoize(cache_fn, fn)
788
+ cache = {}
789
+ _arity(fn.arity, -> *args {
790
+ key = cache_fn.(*args)
791
+ if cache.has_key?(key)
792
+ cache[key]
793
+ else
794
+ result = fn.(*args)
795
+ cache[key] = result
796
+ result
797
+ end
798
+ })
799
+ end
800
+
801
+ def merge(x, y)
802
+ merge_with(-> existing, value { value }, x, y)
803
+ end
804
+
805
+ def merge_all(hashes)
806
+ reduce(merge, {}, hashes)
807
+ end
808
+
809
+ def merge_deep_left(x, y)
810
+ merge_deep_with(-> existing, value { existing }, x, y)
811
+ end
812
+
813
+ def merge_deep_right(x, y)
814
+ merge_deep_with(-> existing, value { value }, x, y)
815
+ end
816
+
817
+ def merge_deep_with(fn, x, y)
818
+ merge_deep_with_key(-> key, existing, value { fn.(existing, value) }, x, y)
819
+ end
820
+
821
+ def merge_deep_with_key(fn, x, y)
822
+ reduce(-> memo, n {
823
+ key, value = n
824
+ memo[key] = if memo.has_key?(key)
825
+ existing = memo[key]
826
+ if existing.is_a?(Hash)
827
+ merge_deep_with_key(fn, existing, value)
828
+ else
829
+ fn.(key, existing, value)
830
+ end
831
+ else
832
+ value
833
+ end
834
+ memo
835
+ }, clone(x), y)
836
+ end
837
+
838
+ def merge_with(fn, x, y)
839
+ merge_with_key(-> key, existing, value { fn.(existing, value) }, x, y)
840
+ end
841
+
842
+ def merge_with_key(fn, x, y)
843
+ reduce(-> memo, n {
844
+ key, value = n
845
+ memo[key] = if memo.has_key?(key)
846
+ existing = memo[key]
847
+ fn.(key, existing, value)
848
+ else
849
+ value
850
+ end
851
+ memo
852
+ }, clone(x), y)
853
+ end
854
+
855
+
856
+ def min(x, y)
857
+ if x < y then x else y end
858
+ end
859
+
860
+ def min_by(fn, list)
861
+ list.map { |x| [fn.(x), x] }
862
+ .to_h
863
+ .sort
864
+ .first[1]
865
+
866
+ end
867
+
868
+ def mul(x, y)
869
+ x * y
870
+ end
871
+
872
+ def n_ary(n, fn)
873
+ _arity(n, -> *args {
874
+ actual_args = if fn.arity == -1 then args else args[0, fn.arity] end
875
+ fn.(*actual_args)
876
+ })
877
+ end
878
+
879
+ def negate(x)
880
+ -x
881
+ end
882
+
883
+ def none?(pred, list)
884
+ !any?(pred, list)
885
+ end
886
+
887
+ def not(x)
888
+ !x
889
+ end
890
+
891
+
892
+ def nth(index, list)
893
+ list[index]
894
+ end
895
+
896
+ def nth_arg(n)
897
+ -> *args {
898
+ args[n]
899
+ }
900
+ end
901
+
902
+ def o(*fns)
903
+ unary(compose(*fns))
904
+ end
905
+
906
+ def hash_of(key, value)
907
+ h = {}
908
+ h[key] = value
909
+ h
910
+ end
911
+
912
+ def of(x)
913
+ [x]
914
+ end
915
+
916
+ def omit(keys, hash)
917
+ hash.reduce({}) { |memo, x|
918
+ k, v = x
919
+ if !keys.include?(k)
920
+ memo[k] = v
921
+ end
922
+ memo
923
+ }
924
+ end
925
+
926
+ def once(fn)
927
+ memoize(-> *args { :once }, fn)
928
+ end
929
+
930
+ def or(x, y)
931
+ x || y
932
+ end
933
+
934
+ def over(lens, fn, x)
935
+ value = fn.(view(lens, x))
936
+ set(lens, value, x)
937
+ end
938
+
939
+ def pair(x, y)
940
+ [x, y]
941
+ end
942
+
943
+ def partial(fn, args)
944
+ apply(curry(fn), args)
945
+ end
946
+
947
+ def partial_right(fn, args)
948
+ pre_args = 0.upto(fn.arity - args.length - 1).map { |i| __ }
949
+ apply(curry(fn), pre_args + args)
950
+ end
951
+
952
+ def partition(predicate, list)
953
+ does_satisfy = []
954
+ does_not_satisfy = []
955
+ for x in list
956
+ if predicate.(x)
957
+ does_satisfy.push(x)
958
+ else
959
+ does_not_satisfy.push(x)
960
+ end
961
+ end
962
+ [does_satisfy, does_not_satisfy]
963
+ end
964
+
965
+ def path(path_elements, x)
966
+ path_elements.reduce(x) { |memo, n|
967
+ memo[n]
968
+ }
969
+ end
970
+
971
+ def prop(key, x)
972
+ x[key]
973
+ end
974
+
975
+ def path_eq?(path_elements, val, x)
976
+ path_satisfies?(equal?(val), path_elements, x)
977
+ end
978
+
979
+ def path_or(path_elements, default, x)
980
+ path_elements.reduce(x) { |memo, n|
981
+ if memo.respond_to?(:has_key?) && memo.has_key?(n)
982
+ memo[n]
983
+ else
984
+ break default
985
+ end
986
+ }
987
+ end
988
+
989
+ def path_satisfies?(fn, path_elements, x)
990
+ fn.(path(path_elements, x))
991
+ end
992
+
993
+ def pick(keys, x)
994
+ keys.reduce({}) { |memo, key|
995
+ memo[key] = x[key] if x.has_key?(key)
996
+ memo
997
+ }
998
+ end
999
+
1000
+ def pick_all(keys, x)
1001
+ keys.reduce({}) { |memo, key|
1002
+ memo[key] = if x.has_key?(key) then x[key] else nil end
1003
+ memo
1004
+ }
1005
+ end
1006
+
1007
+ def pick_by(fn, x)
1008
+ keys(x).reduce({}) { |memo, key|
1009
+ val = x[key]
1010
+ if fn.(key, val)
1011
+ memo[key] = val
1012
+ end
1013
+ memo
1014
+ }
1015
+ end
1016
+
1017
+ def pipe(fn, *fns)
1018
+ curry_n(fn.arity, -> *args {
1019
+ seed = fn.(*args)
1020
+ fns.reduce(seed) { |memo, fn|
1021
+ fn.(memo)
1022
+ }
1023
+ })
1024
+ end
1025
+
1026
+ def pipe_k(fn, *fns)
1027
+ wrapped = map(flat_map, fns)
1028
+ pipe(fn, *wrapped)
1029
+ end
1030
+
1031
+ def pipe_p(fn, *fns)
1032
+ raise "TODO"
1033
+ end
1034
+
1035
+ def pluck(key, f)
1036
+ map(prop(key), f)
1037
+ end
1038
+
1039
+ def prepend(x, list)
1040
+ clone(list).unshift(x)
1041
+ end
1042
+
1043
+ def product(list)
1044
+ reduce(-> memo, x { memo * x }, 1, list)
1045
+ end
1046
+
1047
+ def project(keys, hashes)
1048
+ map(pick(keys), hashes)
1049
+ end
1050
+
1051
+ def prop_eq?(key, val, hash)
1052
+ equal?(val, prop(key, hash))
1053
+ end
1054
+
1055
+ def prop_is?(kind, key, hash)
1056
+ is_a?(kind, prop(key, hash)) == true
1057
+ end
1058
+
1059
+ def prop_or(key, default, hash)
1060
+ if hash.has_key?(key)
1061
+ prop(key, hash)
1062
+ else
1063
+ default
1064
+ end
1065
+ end
1066
+
1067
+ def props(keys, hash)
1068
+ map(prop(__, hash), keys)
1069
+ end
1070
+
1071
+ def prop_satisfies?(fn, key, hash)
1072
+ fn.(prop(key, hash)) == true
1073
+ end
1074
+
1075
+ def range(x, y)
1076
+ x.upto(y - 1).to_a
1077
+ end
1078
+
1079
+ def reduce_by(value_fn, seed, key_fn, list)
1080
+ raise "TODO"
1081
+ end
1082
+
1083
+ def reduce_right(fn, seed, list)
1084
+ result = seed
1085
+ index = list.length - 1
1086
+ while index >= 0
1087
+ result = fn.(list[index], result)
1088
+ index = index - 1
1089
+ end
1090
+ result
1091
+ end
1092
+
1093
+ def reduce_while(predicate, fn, seed, list)
1094
+ list.reduce(seed) { |memo, x|
1095
+ if predicate.(x)
1096
+ fn.(memo, x)
1097
+ else
1098
+ break memo
1099
+ end
1100
+ }
1101
+ end
1102
+
1103
+ def remove(start, count, list)
1104
+ concat(slice(0, start, list), slice(start + count, list.length, list))
1105
+ end
1106
+
1107
+ def repeat(x, n)
1108
+ times(always(x), n)
1109
+ end
1110
+
1111
+ def scan(fn, seed, list)
1112
+ accum = [seed]
1113
+ result = reduce(-> memo, x {
1114
+ r = fn.(memo, x)
1115
+ accum.push(r)
1116
+ r
1117
+ }, seed, list)
1118
+ accum
1119
+ end
1120
+
1121
+ def sequence(of, traversable)
1122
+ raise "TODO"
1123
+ end
1124
+
1125
+ def sub(pattern, replacement, str)
1126
+ str.sub(pattern, replacement)
1127
+ end
1128
+
1129
+ def slice(from, to, list)
1130
+ start_index = max(0, if from < 0 then list.length + from else from end)
1131
+ final_index = min(list.length - 1, if to < 0 then list.length + to - 1 else to - 1 end)
1132
+ result = map(-> i { nth(i, list) }, start_index.upto(final_index))
1133
+ if list.is_a?(String)
1134
+ result.join("")
1135
+ else
1136
+ result
1137
+ end
1138
+ end
1139
+
1140
+ def sort(fn, list)
1141
+ list.sort { |x, y| fn.(x, y) }
1142
+ end
1143
+
1144
+ def sort_by(fn, list)
1145
+ raise "TODO"
1146
+ end
1147
+
1148
+ def sort_with(fn, list)
1149
+ raise "TODO"
1150
+ end
1151
+
1152
+ def split(separator, str)
1153
+ str.split(separator)
1154
+ end
1155
+
1156
+ def split_at(index, list)
1157
+ [slice(0, index, list), slice(index, list.length, list)]
1158
+ end
1159
+
1160
+ def split_every(n, list)
1161
+ indices = (0.upto(list.length - 1)).select { |x| x % n == 0 }
1162
+ indices.map { |i| slice(i, i + n, list) }
1163
+ end
1164
+
1165
+ def split_when(fn, list)
1166
+ first = []
1167
+ second = []
1168
+ split = false
1169
+ for x in list
1170
+ if split
1171
+ second.push(x)
1172
+ else
1173
+ if fn.(x)
1174
+ split = true
1175
+ second.push(x)
1176
+ else
1177
+ first.push(x)
1178
+ end
1179
+ end
1180
+ end
1181
+ [first, second]
1182
+ end
1183
+
1184
+ def starts_with?(start, str)
1185
+ slice(0, start.length, str) == start
1186
+ end
1187
+
1188
+
1189
+ def raise(message)
1190
+ Kernel.raise message
1191
+ end
1192
+
1193
+ def reduce(fn, initial, list)
1194
+ list.reduce(initial) { |memo, x| fn.(memo, x) }
1195
+ end
1196
+
1197
+ def responds_to?(key, x)
1198
+ if x.respond_to?(:respond_to?)
1199
+ x.respond_to?(key)
1200
+ else
1201
+ raise "Cannot `respond_to?` on #{x.class}"
1202
+ end
1203
+ end
1204
+
1205
+ def reverse(list)
1206
+ list.reverse
1207
+ end
1208
+
1209
+ def set(lens, value, x)
1210
+ lens.setter.(value, x)
1211
+ end
1212
+
1213
+ def sub(x, y)
1214
+ x - y
1215
+ end
1216
+
1217
+ def sum(xs)
1218
+ reduce(-> x, y { x + y }, 0, xs)
1219
+ end
1220
+
1221
+ def symmetric_difference(x, y)
1222
+ raise "TODO"
1223
+ end
1224
+
1225
+ def symmetric_difference_with(fn, x, y)
1226
+ raise "TODO"
1227
+ end
1228
+
1229
+ def t(*args)
1230
+ true
1231
+ end
1232
+
1233
+ def tail(list)
1234
+ slice(1, list.length, list)
1235
+ end
1236
+
1237
+ def take(n, list)
1238
+ slice(0, n, list)
1239
+ end
1240
+
1241
+ def take_last(n, list)
1242
+ slice(list.length - n, list.length, list)
1243
+ end
1244
+
1245
+ def take_last_while(fn, list)
1246
+ taken = []
1247
+ for x in list.reverse
1248
+ if fn.(x)
1249
+ taken.unshift(x)
1250
+ else
1251
+ break
1252
+ end
1253
+ end
1254
+ taken
1255
+ end
1256
+
1257
+ def take_while(fn, list)
1258
+ taken = []
1259
+ for x in list
1260
+ if fn.(x)
1261
+ taken.push(x)
1262
+ else
1263
+ break
1264
+ end
1265
+ end
1266
+ taken
1267
+ end
1268
+
1269
+ def tap(fn, x)
1270
+ fn.(x)
1271
+ x
1272
+ end
1273
+
1274
+ def test?(pattern, str)
1275
+ str.match?(pattern)
1276
+ end
1277
+
1278
+ def times(fn, n)
1279
+ map(-> i { fn.(i) }, 0.upto(n - 1))
1280
+ end
1281
+
1282
+ def to_pairs(hash)
1283
+ hash.to_a
1284
+ end
1285
+
1286
+ def to_s(x)
1287
+ x.to_s
1288
+ end
1289
+
1290
+ def to_upper(str)
1291
+ str.upcase
1292
+ end
1293
+
1294
+ def to_lower(str)
1295
+ str.downcase
1296
+ end
1297
+
1298
+ def transduce
1299
+ raise "TODO"
1300
+ end
1301
+
1302
+ def traverse
1303
+ raise "TODO"
1304
+ end
1305
+
1306
+ def trim(str)
1307
+ str.strip
1308
+ end
1309
+
1310
+ def strip(str)
1311
+ trim(str)
1312
+ end
1313
+
1314
+ def begin_rescue(beginner, rescuer)
1315
+ -> *args {
1316
+ begin
1317
+ beginner.(*args)
1318
+ rescue Exception => e
1319
+ rescuer.(e)
1320
+ end
1321
+ }
1322
+ end
1323
+
1324
+ def type(x)
1325
+ x.class
1326
+ end
1327
+
1328
+ def unapply(fn)
1329
+ -> *args {
1330
+ fn.(args)
1331
+ }
1332
+ end
1333
+
1334
+
1335
+ def unary(fn)
1336
+ n_ary(1, fn)
1337
+ end
1338
+
1339
+ def uncurry_n(n, fn)
1340
+ n_ary(n, -> *args {
1341
+ 0.upto(n - 1)
1342
+ .reduce(fn) { |f, x|
1343
+ arg = args[x]
1344
+ f.(arg)
1345
+ }
1346
+ })
1347
+ end
1348
+
1349
+ def unfold(fn, seed)
1350
+ memo = seed
1351
+ results = []
1352
+ while true
1353
+ result = fn.(memo)
1354
+ if result == false
1355
+ break
1356
+ else
1357
+ x, memo = result
1358
+ results.push(x)
1359
+ end
1360
+ end
1361
+ results
1362
+ end
1363
+
1364
+ def union(x, y)
1365
+ union_with(identity, x, y)
1366
+ end
1367
+
1368
+ def union_with(fn, x, y)
1369
+ map_pair = compose(invert, map(fn), from_pairs, map(-> v { [v, v ]}))
1370
+ mapped_x = map_pair.(x)
1371
+ mapped_y = map_pair.(y)
1372
+ merge_deep_left(mapped_x, mapped_y).values
1373
+ end
1374
+
1375
+ def uniq(list)
1376
+ uniq_by(R::identity)
1377
+ end
1378
+
1379
+ def uniq_by(fn, list)
1380
+ found = {}
1381
+ list.select { |x|
1382
+ mapping = fn.(x)
1383
+ if found.has_key?(mapping)
1384
+ false
1385
+ else
1386
+ found[mapping] = true
1387
+ true
1388
+ end
1389
+ }
1390
+ end
1391
+
1392
+ def unless(predicate, fn, x)
1393
+ if !predicate.(x)
1394
+ fn.(x)
1395
+ else
1396
+ x
1397
+ end
1398
+ end
1399
+
1400
+ def unnest(x)
1401
+ flat_map(identity, x)
1402
+ end
1403
+
1404
+ def until(predicate, fn, seed)
1405
+ result = seed
1406
+ while !predicate.(result)
1407
+ result = fn.(result)
1408
+ end
1409
+ result
1410
+ end
1411
+
1412
+ def update(index, x, list)
1413
+ list[index] = x
1414
+ list
1415
+ end
1416
+
1417
+ def use_with(fn, transformers)
1418
+ curry_n(transformers.length, -> *args {
1419
+ values = zip_with_index(args)
1420
+ .map { |x| transformers[x[1]].(x[0]) }
1421
+ apply(fn, values)
1422
+ })
1423
+ end
1424
+
1425
+ def m(sym)
1426
+ curry(method(sym))
1427
+ end
1428
+
1429
+ def pow(x, y)
1430
+ x ** y
1431
+ end
1432
+
1433
+ def values(x)
1434
+ x.values
1435
+ end
1436
+
1437
+ def view(lens, x)
1438
+ lens.getter.(x)
1439
+ end
1440
+
1441
+ def when(predicate, fn, x)
1442
+ if predicate.(x)
1443
+ fn.(x)
1444
+ else
1445
+ x
1446
+ end
1447
+ end
1448
+
1449
+ def where(spec, x)
1450
+ results = spec.map { |k, v|
1451
+ applied = v.(x[k])
1452
+ applied
1453
+ }
1454
+ all?(equal?(true), results)
1455
+ end
1456
+
1457
+ def where_eq(spec, x)
1458
+ where(map(equal?, spec), x)
1459
+ end
1460
+
1461
+ def without(x, y)
1462
+ y - x
1463
+ end
1464
+
1465
+ def xprod(xs, ys)
1466
+ xs.product(ys)
1467
+ end
1468
+
1469
+ def zip(xs, ys)
1470
+ xs.zip(ys)
1471
+ end
1472
+
1473
+ def zip_hash(xs, ys)
1474
+ zip(xs, ys).to_h
1475
+ end
1476
+
1477
+ def zip_with_index(list)
1478
+ zip(list, 0.upto(list.length - 1))
1479
+ end
1480
+
1481
+ def zip_with(fn, xs, ys)
1482
+ 0.upto(xs.length - 1)
1483
+ .map { |x| fn.(xs[x], ys[x]) }
1484
+ end
1485
+
1486
+ end