fun-ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +42 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +42 -0
- data/.ruby-version +1 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +61 -0
- data/LICENSE.txt +21 -0
- data/Makefile +5 -0
- data/README.md +19 -0
- data/Rakefile +16 -0
- data/bin/console +16 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/bin/setup +8 -0
- data/bin/yard +29 -0
- data/bin/yardoc +29 -0
- data/bin/yri +29 -0
- data/fun-ruby.gemspec +35 -0
- data/lib/fun-ruby.rb +4 -0
- data/lib/fun_ruby/all.rb +8 -0
- data/lib/fun_ruby/array.rb +63 -0
- data/lib/fun_ruby/common/helpers.rb +19 -0
- data/lib/fun_ruby/container/define.rb +66 -0
- data/lib/fun_ruby/container/mixin.rb +28 -0
- data/lib/fun_ruby/container/resolve.rb +57 -0
- data/lib/fun_ruby/container.rb +55 -0
- data/lib/fun_ruby/enum.rb +214 -0
- data/lib/fun_ruby/file.rb +32 -0
- data/lib/fun_ruby/function.rb +157 -0
- data/lib/fun_ruby/hash.rb +559 -0
- data/lib/fun_ruby/string.rb +72 -0
- data/lib/fun_ruby/version.rb +5 -0
- data/lib/fun_ruby.rb +87 -0
- metadata +165 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../fun_ruby"
|
4
|
+
require_relative "../function"
|
5
|
+
|
6
|
+
module FunRuby
|
7
|
+
module Common
|
8
|
+
# Helpers for shorting internal implementations
|
9
|
+
module Helpers
|
10
|
+
private
|
11
|
+
|
12
|
+
def curry_implementation(method_name, *args)
|
13
|
+
F::Function.curry(method("_#{method_name}")).(*args)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private_constant :Common
|
19
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../container"
|
4
|
+
require_relative "../container/resolve"
|
5
|
+
|
6
|
+
module FunRuby
|
7
|
+
class Container
|
8
|
+
# @private
|
9
|
+
class Define
|
10
|
+
private_class_method :new
|
11
|
+
|
12
|
+
# @private
|
13
|
+
def self.build(container: FunRuby.container, namespaces: [])
|
14
|
+
raise TypeError, "namespaces: should be an array" unless namespaces.is_a?(::Array)
|
15
|
+
raise TypeError, "container: should be an instance of #{Container.name}" unless container.is_a?(Container)
|
16
|
+
|
17
|
+
namespaces = namespaces.map(&:to_s)
|
18
|
+
resolve = Resolve.build(
|
19
|
+
container: container,
|
20
|
+
aliases: (0...namespaces.size).reduce([]) do |combos, index|
|
21
|
+
[namespaces[0..index].join(NAMESPACE_SEPARATOR), *combos]
|
22
|
+
end
|
23
|
+
)
|
24
|
+
new(
|
25
|
+
container: container,
|
26
|
+
namespaces: namespaces,
|
27
|
+
resolve: resolve
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @private
|
32
|
+
def initialize(container:, namespaces:, resolve:)
|
33
|
+
@container = container
|
34
|
+
@namespaces = namespaces
|
35
|
+
@resolve = resolve
|
36
|
+
end
|
37
|
+
|
38
|
+
# @private
|
39
|
+
def namespace(namespace, &block)
|
40
|
+
self.class.build(container: container, namespaces: [*namespaces, namespace]).tap do |define|
|
41
|
+
define.instance_exec(&block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @private
|
46
|
+
def call(&block)
|
47
|
+
instance_exec(&block)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @private
|
51
|
+
def function(key, &block)
|
52
|
+
if block.nil?
|
53
|
+
resolve.(key)
|
54
|
+
else
|
55
|
+
full_key = [*namespaces, key].join(NAMESPACE_SEPARATOR)
|
56
|
+
container.define(full_key, &block)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
alias f function
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
attr_reader :container, :namespaces, :resolve
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "resolve"
|
4
|
+
|
5
|
+
module FunRuby
|
6
|
+
class Container
|
7
|
+
# @private
|
8
|
+
module Mixin
|
9
|
+
# @private
|
10
|
+
def self.build(aliases:)
|
11
|
+
mixin = Module.new
|
12
|
+
mixin.send(:define_method, :_resolve) { Resolve.build(aliases: aliases) }
|
13
|
+
mixin.send(:include, self)
|
14
|
+
mixin
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def _resolve
|
20
|
+
@_resolve ||= Resolve.build
|
21
|
+
end
|
22
|
+
|
23
|
+
def f(key)
|
24
|
+
_resolve.(key)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FunRuby
|
4
|
+
class Container
|
5
|
+
# @private
|
6
|
+
class Resolve
|
7
|
+
private_class_method :new
|
8
|
+
|
9
|
+
# @private
|
10
|
+
def self.build(aliases: [], container: FunRuby.container)
|
11
|
+
formatted = aliases.each_with_object({}) do |key, namespace|
|
12
|
+
if key.is_a?(::Hash)
|
13
|
+
namespace.merge!(key.map { |k, v| [k, v].map(&:to_s) }.to_h)
|
14
|
+
else
|
15
|
+
namespace[key.to_s] = nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
new(aliases: formatted.to_a.reverse.to_h, container: container)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @private
|
22
|
+
def initialize(aliases:, container:)
|
23
|
+
@aliases = aliases
|
24
|
+
@container = container
|
25
|
+
end
|
26
|
+
|
27
|
+
# @private
|
28
|
+
def call(key)
|
29
|
+
key = key.to_s
|
30
|
+
aliases.each do |(namespace, shortcut)|
|
31
|
+
full_key = build_full_key(namespace, shortcut, key)
|
32
|
+
begin
|
33
|
+
return container.fetch(full_key)
|
34
|
+
rescue KeyError
|
35
|
+
next
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
container.fetch(key) do
|
40
|
+
raise KeyError, "key #{key.inspect} has not been registered"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def build_full_key(namespace, shortcut, key)
|
47
|
+
if shortcut.nil?
|
48
|
+
[namespace, key].join(NAMESPACE_SEPARATOR)
|
49
|
+
else
|
50
|
+
key.gsub(/(?<=\A|\.)#{shortcut}(?=\z|\.)/, namespace)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :aliases, :container
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "container/mixin"
|
4
|
+
|
5
|
+
module FunRuby
|
6
|
+
# @private
|
7
|
+
class Container
|
8
|
+
NAMESPACE_SEPARATOR = "."
|
9
|
+
NOT_EVALUATED = Object.new.freeze
|
10
|
+
|
11
|
+
# @private
|
12
|
+
def initialize
|
13
|
+
@storage = {}
|
14
|
+
@mutex = Mutex.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# @private
|
18
|
+
def define(key, &block)
|
19
|
+
key = key.to_s
|
20
|
+
|
21
|
+
raise KeyError, "#{key.inspect} is already defined" if storage.key?(key)
|
22
|
+
raise TypeError, "block should be given" unless block_given?
|
23
|
+
|
24
|
+
storage[key] = init_meta(block)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @private
|
28
|
+
def fetch(key)
|
29
|
+
key = key.to_s
|
30
|
+
meta = storage.fetch(key)
|
31
|
+
value = storage[key].fetch(:value)
|
32
|
+
return value unless value.equal?(NOT_EVALUATED)
|
33
|
+
|
34
|
+
meta.fetch(:definition).().tap do |evaluated|
|
35
|
+
storage[key][:value] = evaluated
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @private
|
40
|
+
def import(*aliases)
|
41
|
+
Mixin.build(aliases: aliases)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :storage, :mutex
|
47
|
+
|
48
|
+
def init_meta(definition)
|
49
|
+
{
|
50
|
+
definition: definition,
|
51
|
+
value: NOT_EVALUATED
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "common/helpers"
|
4
|
+
|
5
|
+
module FunRuby
|
6
|
+
# Module containing methods for enumerables
|
7
|
+
module Enum
|
8
|
+
include Common::Helpers
|
9
|
+
|
10
|
+
extend self
|
11
|
+
|
12
|
+
# Returns a boolean which represents if all results
|
13
|
+
# of applying a function to each element are truthy
|
14
|
+
#
|
15
|
+
# @since 0.1.0
|
16
|
+
#
|
17
|
+
# @param function [#call/1]
|
18
|
+
# @param enumerable [#to_enum]
|
19
|
+
#
|
20
|
+
# @return [Boolean]
|
21
|
+
#
|
22
|
+
# @example Basic returning true
|
23
|
+
# F::Enum.all?(->(x) { x % 2 == 0 }, [2, 4, 6]) #=> true
|
24
|
+
#
|
25
|
+
# @example Basic returning false
|
26
|
+
# F::Enum.all?(->(x) { x % 2 == 0 }, [2, 5, 6]) #=> false
|
27
|
+
#
|
28
|
+
# @example Curried
|
29
|
+
# curried = F::Enum.all?
|
30
|
+
# curried.(->(x) { x % 2 == 0 }).([1, 2, 3]) #=> false
|
31
|
+
#
|
32
|
+
# @example Curried with placeholder
|
33
|
+
# curried = F::Enum.all?(F._, [2, 4, 6])
|
34
|
+
# curried.(->(x) { x % 2 == 0 }) # => true
|
35
|
+
def all?(function = F._, enumerable = F._)
|
36
|
+
curry_implementation(:all?, function, enumerable)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Applies a function to each element of an enumerable and
|
40
|
+
# returns the initial enumerable
|
41
|
+
#
|
42
|
+
# @since 0.1.0
|
43
|
+
#
|
44
|
+
# @param function [#call/1]
|
45
|
+
# @param enumerable [#to_enum]
|
46
|
+
#
|
47
|
+
# @return [::Array] the passed enumerable
|
48
|
+
#
|
49
|
+
# @example Basic
|
50
|
+
# F::Enum.each(->(x) { puts x }, [1, 2, 3]) #=> [1, 2, 3]
|
51
|
+
#
|
52
|
+
# @example Curried
|
53
|
+
# curried = F::Enum.each
|
54
|
+
# curried.(->(x) { puts x }).([1, 2, 3]) #=> [1, 2, 3]
|
55
|
+
#
|
56
|
+
# @example Curried with placeholder
|
57
|
+
# curried = F::Enum.each(F._, [1, 2, 3])
|
58
|
+
# curried.(->(x) { puts x }) #=> [1, 2, 3]
|
59
|
+
def each(function = F._, enumerable = F._)
|
60
|
+
curry_implementation(:each, function, enumerable)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns a new enumerable with new values calculated
|
64
|
+
# by applying a function to each element of a passed enumerable
|
65
|
+
#
|
66
|
+
# @since 0.1.0
|
67
|
+
#
|
68
|
+
# @param function [#call/1]
|
69
|
+
# @param enumerable [#to_enum]
|
70
|
+
#
|
71
|
+
# @return [::Array] a new enumerable with calculated values
|
72
|
+
#
|
73
|
+
# @example Basic
|
74
|
+
# F::Enum.map(->(x) { x * 2 }, [1, 2, 3]) #=> [2, 4, 6]
|
75
|
+
#
|
76
|
+
# @example Curried
|
77
|
+
# curried = F::Enum.map
|
78
|
+
# curried.(->(x) { x * 2 }).([1, 2, 3]) #=> [2, 4, 6]
|
79
|
+
#
|
80
|
+
# @example Curried with placeholder
|
81
|
+
# curried = F::Enum.map(F._, [1, 2, 3])
|
82
|
+
# curried.(->(x) { x * 2 }) # => [2, 4, 6]
|
83
|
+
def map(function = F._, enumerable = F._)
|
84
|
+
curry_implementation(:map, function, enumerable)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns a new enumerable containing only these elements
|
88
|
+
# results of calling a function on them are truthy
|
89
|
+
#
|
90
|
+
# @since 0.1.0
|
91
|
+
#
|
92
|
+
# @param function [#call/1]
|
93
|
+
# @param enumerable [#to_enum]
|
94
|
+
#
|
95
|
+
# @return [::Array] a new enumerable with selected values
|
96
|
+
#
|
97
|
+
# @example Basic
|
98
|
+
# F::Enum.select(->(x) { x % 2 == 0 }, [1, 2, 3, 4, 5]) #=> [2, 4]
|
99
|
+
#
|
100
|
+
# @example Curried
|
101
|
+
# curried = F::Enum.select
|
102
|
+
# curried.(->(x) { x % 2 == 0 }).( [1, 2, 3, 4, 5]) #=> [2, 4]
|
103
|
+
#
|
104
|
+
# @example Curried with placeholder
|
105
|
+
# curried = F::Enum.select(F._, [1, 2, 3, 4, 5])
|
106
|
+
# curried.(->(x) { x % 2 == 0 }) #=> [2, 4]
|
107
|
+
def select(function = F._, enumerable = F._)
|
108
|
+
curry_implementation(:select, function, enumerable)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns a single item by iterating through the list, successively
|
112
|
+
# calling the iterator function and passing it an accumulator value
|
113
|
+
# and the current value from the array, and then passing the result
|
114
|
+
# to the next call.
|
115
|
+
#
|
116
|
+
# @since 0.1.0
|
117
|
+
#
|
118
|
+
# @param function [#call/2] ->(accumulator, element) {}
|
119
|
+
# @param accumulator Object
|
120
|
+
# @param enumerable [#to_enum]
|
121
|
+
#
|
122
|
+
# @return Object a result of reducing the enumerable
|
123
|
+
#
|
124
|
+
# @example Basic: sum of all elements
|
125
|
+
# F::Enum.reduce(->(acc, x) { acc + x }, 0, [1, 2, 3]) #=> 6
|
126
|
+
#
|
127
|
+
# @example Basic: subtraction of all elements
|
128
|
+
# F::Enum.reduce(->(acc, x) { acc - x }, 0, [1, 2, 3]) #=> -6
|
129
|
+
#
|
130
|
+
# @example Basic: histogram
|
131
|
+
# F::Enum.reduce(
|
132
|
+
# ->(acc, x) { acc[x] += 1; acc },
|
133
|
+
# Hash.new(0),
|
134
|
+
# [1, 1, 1, 2, 2, 3]
|
135
|
+
# ) #=> { 1 => 3, 2 => 2, 3 => 1 }
|
136
|
+
#
|
137
|
+
# @example Curried: sum
|
138
|
+
# curried = F::Enum.reduce
|
139
|
+
# curried.(->(acc, x) { acc + x }).(0).([1, 2, 3]) #=> 6
|
140
|
+
#
|
141
|
+
# @example Curried: sum with placeholder for function
|
142
|
+
# curried = F::Enum.reduce(F._, 0, [1, 2, 3])
|
143
|
+
# curried.(->(acc, x) { acc + x }) #=> 6
|
144
|
+
#
|
145
|
+
# @example Curried: sum with placeholder for accumulator
|
146
|
+
# curried = F::Enum.reduce(->(acc, x) { acc + x }, F._, [1, 2, 3])
|
147
|
+
# curried.(0) #=> 6
|
148
|
+
#
|
149
|
+
# @example Curried: sum with placeholder for function and accumulator
|
150
|
+
# curried = F::Enum.reduce(F._, F._, [1, 2, 3])
|
151
|
+
# curried.(->(acc, x) { acc + x }, 0) # => 6
|
152
|
+
# curried.(->(acc, x) { acc + x }).(0) # => 6
|
153
|
+
def reduce(function = F._, accumulator = F._, enumerable = F._)
|
154
|
+
curry_implementation(:reduce, function, accumulator, enumerable)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Goes through a given enumerable and count the amount of elements
|
158
|
+
# for which a passed function returned true
|
159
|
+
#
|
160
|
+
# @since 0.1.0
|
161
|
+
#
|
162
|
+
# @param function [#call/1] ->(item) { returns Boolean }
|
163
|
+
# @param enumerable [#to_enum]
|
164
|
+
#
|
165
|
+
# @return [Integer]
|
166
|
+
#
|
167
|
+
# @example Basic: count odd elements
|
168
|
+
# F::Enum.count(->(x) { x % 2 == 1 }, [1, 2, 3]) #=> 2
|
169
|
+
#
|
170
|
+
# @example Curried: count odd elements
|
171
|
+
# curried = F::Enum.count
|
172
|
+
# curried.(->(x) { x % 2 == 1 }).([1, 2, 3]) #=> 2
|
173
|
+
#
|
174
|
+
# @example Curried with placeholder: count odd elements
|
175
|
+
# curried = F::Enum.count(F._, [1, 2, 3])
|
176
|
+
# curried.(->(x) { x % 2 == 1 }) #=> 2
|
177
|
+
#
|
178
|
+
# curried = F::Enum.count(->(x) { x % 2 == 1 }, F._)
|
179
|
+
# curried.([1, 2, 3]) #=> 2
|
180
|
+
def count(function = F._, enumerable = F._)
|
181
|
+
curry_implementation(:count, function, enumerable)
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def _all?(function, enumerable)
|
187
|
+
_enum(enumerable).all?(&function)
|
188
|
+
end
|
189
|
+
|
190
|
+
def _each(function, enumerable)
|
191
|
+
_enum(enumerable).each(&function)
|
192
|
+
end
|
193
|
+
|
194
|
+
def _map(function, enumerable)
|
195
|
+
_enum(enumerable).map(&function)
|
196
|
+
end
|
197
|
+
|
198
|
+
def _select(function, enumerable)
|
199
|
+
_enum(enumerable).select(&function)
|
200
|
+
end
|
201
|
+
|
202
|
+
def _reduce(function, accumulator, enumerable)
|
203
|
+
_enum(enumerable).reduce(accumulator, &function)
|
204
|
+
end
|
205
|
+
|
206
|
+
def _count(function, enumerable)
|
207
|
+
_enum(enumerable).count(&function)
|
208
|
+
end
|
209
|
+
|
210
|
+
def _enum(enumerable)
|
211
|
+
enumerable.to_enum
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FunRuby
|
4
|
+
# Module containing methods for files
|
5
|
+
module File
|
6
|
+
include Common::Helpers
|
7
|
+
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# Writes a given content to a given file
|
11
|
+
#
|
12
|
+
# @since 0.1.0
|
13
|
+
#
|
14
|
+
# @param filepath [::String]
|
15
|
+
# @param content [::String]
|
16
|
+
#
|
17
|
+
# @example Basics
|
18
|
+
# tmp = Tempfile.new("file.txt")
|
19
|
+
# F::File.write(tmp.path, "abc")
|
20
|
+
# File.read(tmp.path) #=> "abc"
|
21
|
+
# tmp.unlink
|
22
|
+
def write(filepath = F._, content = F._)
|
23
|
+
curry_implementation(:write, filepath, content)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def _write(filepath, content)
|
29
|
+
::File.open(filepath, "w") { |file| file.write(content) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "common/helpers"
|
4
|
+
|
5
|
+
# Top-level
|
6
|
+
module FunRuby
|
7
|
+
# Module for useful manipulations with functions
|
8
|
+
module Function
|
9
|
+
extend self
|
10
|
+
|
11
|
+
# Performs right-to-left function composition
|
12
|
+
#
|
13
|
+
# @since 0.1.0
|
14
|
+
#
|
15
|
+
# @param required [#call]
|
16
|
+
# @param *functions [#call]
|
17
|
+
#
|
18
|
+
# @return [#call]
|
19
|
+
#
|
20
|
+
# @example Basic: one function
|
21
|
+
# plus_2 = ->(x) { x + 2 }
|
22
|
+
#
|
23
|
+
# curried = F::Function.compose(plus_2)
|
24
|
+
# curried.(2) # => 4
|
25
|
+
#
|
26
|
+
# @example Basic: two functions
|
27
|
+
# plus_2 = ->(x) { x + 2 }
|
28
|
+
# multiply_3 = ->(x) { x * 3 }
|
29
|
+
#
|
30
|
+
# # multiplying -> adding
|
31
|
+
# curried = F::Function.compose(plus_2, multiply_3)
|
32
|
+
#
|
33
|
+
# curried.(2) # => 8
|
34
|
+
#
|
35
|
+
# # adding -> multiplying
|
36
|
+
# curried = F::Function.compose(multiply_3, plus_2)
|
37
|
+
#
|
38
|
+
# curried.(2) # => 12
|
39
|
+
#
|
40
|
+
# @example Basic: three functions
|
41
|
+
# plus_2 = ->(x) { x + 2 }
|
42
|
+
# multiply_3 = ->(x) { x * 3 }
|
43
|
+
# mod_4 = ->(x) { x % 4 }
|
44
|
+
#
|
45
|
+
# # multiplying -> adding -> mod
|
46
|
+
# curried = F::Function.compose(mod_4, plus_2, multiply_3)
|
47
|
+
# curried.(4) # => 2
|
48
|
+
def compose(required = F._, *functions)
|
49
|
+
curry(method(:_compose)).(required, *functions)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Performs left-to-right function composition
|
53
|
+
#
|
54
|
+
# @since 0.1.0
|
55
|
+
#
|
56
|
+
# @param required [#call]
|
57
|
+
# @param *functions [#call]
|
58
|
+
#
|
59
|
+
# @return [#call]
|
60
|
+
#
|
61
|
+
# @example Basic: one function
|
62
|
+
# plus_2 = ->(x) { x + 2 }
|
63
|
+
#
|
64
|
+
# curried = F::Function.pipe(plus_2)
|
65
|
+
# curried.(2) # => 4
|
66
|
+
#
|
67
|
+
# @example Basic: two functions
|
68
|
+
# plus_2 = ->(x) { x + 2 }
|
69
|
+
# multiply_3 = ->(x) { x * 3 }
|
70
|
+
#
|
71
|
+
# # multiplying -> adding
|
72
|
+
# curried = F::Function.pipe(multiply_3, plus_2)
|
73
|
+
#
|
74
|
+
# curried.(2) # => 8
|
75
|
+
#
|
76
|
+
# # adding -> multiplying
|
77
|
+
# curried = F::Function.pipe(plus_2, multiply_3)
|
78
|
+
#
|
79
|
+
# curried.(2) # => 12
|
80
|
+
#
|
81
|
+
# @example Basic: three functions
|
82
|
+
# plus_2 = ->(x) { x + 2 }
|
83
|
+
# multiply_3 = ->(x) { x * 3 }
|
84
|
+
# mod_4 = ->(x) { x % 4 }
|
85
|
+
#
|
86
|
+
# # multiplying -> adding -> mod
|
87
|
+
# curried = F::Function.pipe(multiply_3, plus_2, mod_4)
|
88
|
+
# curried.(4) # => 2
|
89
|
+
def pipe(required = F._, *functions)
|
90
|
+
curry(method(:_pipe)).(required, *functions)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @example
|
94
|
+
# build_ary = ->(a, b, c) { [a, b, c] }
|
95
|
+
# curry = F::Function.curry
|
96
|
+
# curried = curry.(build_ary)
|
97
|
+
#
|
98
|
+
# curried.(1, 2, 3) # => [1, 2, 3]
|
99
|
+
# curried.(1).(2).(3) # => [1, 2, 3]
|
100
|
+
# curried.(F._, 2).(F._, 3).(1) # => [1, 2, 3]
|
101
|
+
# curried.(F._, 2, F._).(1).(3) # => [1, 2, 3]
|
102
|
+
# curried.(F._, F._, 3).(F._, 2).(1) # => [1, 2, 3]
|
103
|
+
# curried.(F._, 2, F._).(1, 3) # => [1, 2, 3]
|
104
|
+
# curried.(F._, 2, F._).(1, 3) # => [1, 2, 3]
|
105
|
+
def curry(function = F._)
|
106
|
+
handling_placeholders(method(:_curry).curry, [function])
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def _pipe(required, *functions)
|
112
|
+
functions = [required, *functions]
|
113
|
+
functions.reduce do |composed, function|
|
114
|
+
->(*args) { function.(composed.(*args)) }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def _compose(required, *functions)
|
119
|
+
_pipe(*functions, required)
|
120
|
+
end
|
121
|
+
|
122
|
+
def _curry(function)
|
123
|
+
handling_placeholders(function.curry)
|
124
|
+
end
|
125
|
+
|
126
|
+
def handling_placeholders(function, processed_args = [], *new_args)
|
127
|
+
processed_args, _ = new_args.reduce([processed_args, 0], &method(:apply_arg))
|
128
|
+
values, with_placeholders = bisect_args(processed_args)
|
129
|
+
result = function.(*values)
|
130
|
+
return result unless result.respond_to?(:call)
|
131
|
+
|
132
|
+
->(*args) { handling_placeholders(result, with_placeholders, *args) }
|
133
|
+
end
|
134
|
+
|
135
|
+
def apply_arg((processed_args, shift), new_arg)
|
136
|
+
processed_args = processed_args.dup
|
137
|
+
placeholder_index = processed_args[shift..-1].to_a.index(F._)
|
138
|
+
if placeholder_index
|
139
|
+
real_position = placeholder_index + shift
|
140
|
+
processed_args[real_position] = new_arg
|
141
|
+
[processed_args, real_position + 1]
|
142
|
+
else
|
143
|
+
processed_args << new_arg
|
144
|
+
[processed_args, processed_args.size]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def bisect_args(args)
|
149
|
+
index = args.index(F._)
|
150
|
+
return [args, []] if index.nil?
|
151
|
+
|
152
|
+
values = args[0...index]
|
153
|
+
placeholders = args[index..-1] || []
|
154
|
+
[values, placeholders]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|