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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +35 -0
- data/README.md +35 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/feldspar.gemspec +26 -0
- data/lib/feldspar.rb +18 -0
- data/lib/feldspar/add.rb +8 -0
- data/lib/feldspar/add_index.rb +15 -0
- data/lib/feldspar/adjust.rb +10 -0
- data/lib/feldspar/all.rb +8 -0
- data/lib/feldspar/all_pass.rb +12 -0
- data/lib/feldspar/always.rb +8 -0
- data/lib/feldspar/curry.rb +91 -0
- data/lib/feldspar/filter.rb +8 -0
- data/lib/feldspar/identity.rb +8 -0
- data/lib/feldspar/map.rb +16 -0
- data/lib/feldspar/reject.rb +8 -0
- data/lib/feldspar/splitme.rb +1486 -0
- data/lib/feldspar/version.rb +3 -0
- data/script/newfn +7 -0
- metadata +110 -0
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
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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
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
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
|
data/lib/feldspar/add.rb
ADDED
data/lib/feldspar/all.rb
ADDED
@@ -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
|
data/lib/feldspar/map.rb
ADDED
@@ -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,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
|