fun-ruby 0.0.1
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/.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
|