j8-functional 0.1.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 +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +30 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +73 -0
- data/LICENSE.txt +21 -0
- data/README.md +142 -0
- data/Rakefile +51 -0
- data/bin/console +55 -0
- data/bin/setup +8 -0
- data/j8-functional.gemspec +38 -0
- data/lib/j8-functional.rb +3 -0
- data/lib/j8/bi_consumer.rb +22 -0
- data/lib/j8/bi_function.rb +17 -0
- data/lib/j8/bi_predicate.rb +27 -0
- data/lib/j8/binary_operator.rb +17 -0
- data/lib/j8/collector.rb +6 -0
- data/lib/j8/collectors.rb +6 -0
- data/lib/j8/common.rb +60 -0
- data/lib/j8/comparator.rb +25 -0
- data/lib/j8/consumer.rb +22 -0
- data/lib/j8/function.rb +27 -0
- data/lib/j8/functional.rb +26 -0
- data/lib/j8/functional/exceptions.rb +7 -0
- data/lib/j8/functional/version.rb +7 -0
- data/lib/j8/optional.rb +105 -0
- data/lib/j8/predicate.rb +31 -0
- data/lib/j8/stream.rb +177 -0
- data/lib/j8/supplier.rb +11 -0
- metadata +187 -0
data/bin/setup
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'j8/functional/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'j8-functional'
|
9
|
+
spec.version = J8::Functional::VERSION
|
10
|
+
spec.authors = ['John Shields']
|
11
|
+
spec.email = ['john@shields.wtf']
|
12
|
+
|
13
|
+
spec.summary = 'Java 8 inspired functional utilities'
|
14
|
+
spec.homepage = 'https://john.shields.wtf'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
|
18
|
+
|
19
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = 'exe'
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ['lib']
|
29
|
+
|
30
|
+
spec.add_development_dependency 'bundler'
|
31
|
+
spec.add_development_dependency 'gem-release'
|
32
|
+
spec.add_development_dependency 'pry'
|
33
|
+
spec.add_development_dependency 'pry-doc'
|
34
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
35
|
+
spec.add_development_dependency 'rb-inotify'
|
36
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
37
|
+
spec.add_development_dependency 'rubocop'
|
38
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module J8
|
4
|
+
class BiConsumer
|
5
|
+
include J8::Common
|
6
|
+
|
7
|
+
def accept(o1, o2)
|
8
|
+
@callable.call(o1, o2)
|
9
|
+
end
|
10
|
+
|
11
|
+
def then(after = nil, &block)
|
12
|
+
callable = from_callable(after, block)
|
13
|
+
|
14
|
+
J8::BiConsumer.new(
|
15
|
+
lambda do |o1, o2|
|
16
|
+
accept(o1, o2)
|
17
|
+
callable.accept(o1, o2)
|
18
|
+
end
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module J8
|
4
|
+
class BiFunction
|
5
|
+
include J8::Common
|
6
|
+
|
7
|
+
def apply(o1, o2)
|
8
|
+
@callable.call(o1, o2)
|
9
|
+
end
|
10
|
+
|
11
|
+
def then(after = nil, &block)
|
12
|
+
callable = from_callable(after, block)
|
13
|
+
|
14
|
+
J8::BiFunction.new(->(o1, o2) { callable.apply(*apply(o1, o2)) })
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module J8
|
4
|
+
class BiPredicate
|
5
|
+
include J8::Common
|
6
|
+
|
7
|
+
def test(o1, o2)
|
8
|
+
@callable.call(o1, o2)
|
9
|
+
end
|
10
|
+
|
11
|
+
def negate
|
12
|
+
J8::BiPredicate.new(->(o1, o2) { !test(o1, o2) })
|
13
|
+
end
|
14
|
+
|
15
|
+
def and(other = nil, &block)
|
16
|
+
callable = from_callable(other, block)
|
17
|
+
|
18
|
+
J8::BiPredicate.new(->(o1, o2) { test(o1, o2) && callable.test(o1, o2) })
|
19
|
+
end
|
20
|
+
|
21
|
+
def or(other = nil, &block)
|
22
|
+
callable = from_callable(other, block)
|
23
|
+
|
24
|
+
J8::BiPredicate.new(->(o1, o2) { test(o1, o2) || callable.test(o1, o2) })
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module J8
|
4
|
+
class BinaryOperator < J8::BiFunction
|
5
|
+
def self.max_by(comparator = nil, &block)
|
6
|
+
callable = from_callable_class(comparator, block, J8::Comparator)
|
7
|
+
|
8
|
+
J8::BinaryOperator.new(->(a, b) { callable.compare(a, b) <= 0 ? a : b })
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.min_by(comparator = nil, &block)
|
12
|
+
callable = from_callable_class(comparator, block, J8::Comparator)
|
13
|
+
|
14
|
+
J8::BinaryOperator.new(->(a, b) { callable.compare(a, b) >= 0 ? a : b })
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/j8/collector.rb
ADDED
data/lib/j8/common.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module J8
|
4
|
+
module Common
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def initialize(callable = nil, &block)
|
8
|
+
@callable = callable_from_proc(callable, block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def lambda?(o)
|
12
|
+
o.is_a?(Proc) && o.lambda?
|
13
|
+
end
|
14
|
+
|
15
|
+
def raise_unless_lambda(o)
|
16
|
+
return if lambda?(o)
|
17
|
+
|
18
|
+
raise ArgumentError, "(#{o.class}) '#{o}' is not a lambda"
|
19
|
+
end
|
20
|
+
|
21
|
+
def from_callable(callable, block)
|
22
|
+
callable = callable_from_proc(callable, block)
|
23
|
+
|
24
|
+
if callable.is_a?(self.class)
|
25
|
+
callable
|
26
|
+
else
|
27
|
+
raise_unless_lambda(callable)
|
28
|
+
|
29
|
+
self.class.new(callable)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def from_callable_class(callable, block, klass)
|
34
|
+
if callable.is_a?(klass)
|
35
|
+
callable
|
36
|
+
else
|
37
|
+
callable = callable_from_proc(callable, block)
|
38
|
+
raise_unless_lambda(callable)
|
39
|
+
|
40
|
+
klass.new(callable)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def make_lambda(block)
|
45
|
+
Object.new.tap { |n| n.define_singleton_method(:_, block) }.method(:_).to_proc
|
46
|
+
end
|
47
|
+
|
48
|
+
def callable_from_proc(callable, block)
|
49
|
+
if block.nil?
|
50
|
+
raise J8::NilException if callable.nil?
|
51
|
+
|
52
|
+
raise_unless_lambda(callable)
|
53
|
+
|
54
|
+
callable
|
55
|
+
else
|
56
|
+
make_lambda(block)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module J8
|
4
|
+
class Comparator
|
5
|
+
include J8::Common
|
6
|
+
|
7
|
+
def self.comparing(extractor, comparator)
|
8
|
+
extractor = from_callable_class(extractor, nil, J8::Function)
|
9
|
+
comparator = from_callable_class(comparator, nil, J8::Comparator)
|
10
|
+
|
11
|
+
J8::Comparataor.new(
|
12
|
+
lambda do |o1, o2|
|
13
|
+
comparator.compare(extractor.apply(o1), extractor.apply(o2))
|
14
|
+
end
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def compare(o1, o2)
|
19
|
+
raise J8::NilException if o1.nil?
|
20
|
+
raise J8::NilException if o2.nil?
|
21
|
+
|
22
|
+
@callable.call(o1, o2)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/j8/consumer.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module J8
|
4
|
+
class Consumer
|
5
|
+
include J8::Common
|
6
|
+
|
7
|
+
def accept(o)
|
8
|
+
@callable.call(o)
|
9
|
+
end
|
10
|
+
|
11
|
+
def then(after = nil, &block)
|
12
|
+
callable = from_callable(after, block)
|
13
|
+
|
14
|
+
J8::Consumer.new(
|
15
|
+
lambda do |o|
|
16
|
+
accept(o)
|
17
|
+
callable.accept(o)
|
18
|
+
end
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/j8/function.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module J8
|
4
|
+
class Function
|
5
|
+
include J8::Common
|
6
|
+
|
7
|
+
def apply(o)
|
8
|
+
@callable.call(o)
|
9
|
+
end
|
10
|
+
|
11
|
+
def compose(before = nil, &block)
|
12
|
+
callable = from_callable(before, block)
|
13
|
+
|
14
|
+
J8::Function.new(->(o) { apply(callable.apply(o)) })
|
15
|
+
end
|
16
|
+
|
17
|
+
def then(after = nil, &block)
|
18
|
+
callable = from_callable(after, block)
|
19
|
+
|
20
|
+
J8::Function.new(->(o) { callable.apply(apply(o)) })
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.identity
|
24
|
+
J8::Function.new { |o| o }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'j8/functional/version'
|
4
|
+
require 'j8/functional/exceptions'
|
5
|
+
require 'j8/common'
|
6
|
+
|
7
|
+
require 'j8/bi_consumer'
|
8
|
+
require 'j8/bi_function'
|
9
|
+
require 'j8/predicate'
|
10
|
+
require 'j8/collector'
|
11
|
+
require 'j8/supplier'
|
12
|
+
require 'j8/optional'
|
13
|
+
require 'j8/function'
|
14
|
+
require 'j8/consumer'
|
15
|
+
require 'j8/comparator'
|
16
|
+
require 'j8/stream'
|
17
|
+
|
18
|
+
class Object
|
19
|
+
def j8_stream
|
20
|
+
if self.class.ancestors.include?(Enumerable)
|
21
|
+
J8::Stream.new(each)
|
22
|
+
else
|
23
|
+
J8::Stream.new([self].each)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/j8/optional.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module J8
|
4
|
+
class Optional
|
5
|
+
include J8::Common
|
6
|
+
|
7
|
+
def initialize(value = nil)
|
8
|
+
@value = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.empty
|
12
|
+
J8::Optional.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.of(value)
|
16
|
+
raise J8::NilException if value.nil?
|
17
|
+
|
18
|
+
J8::Optional.new(value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.of_nilable(value)
|
22
|
+
J8::Optional.new(value)
|
23
|
+
end
|
24
|
+
|
25
|
+
def empty?
|
26
|
+
@value.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
def equals?(object)
|
30
|
+
@value == object
|
31
|
+
end
|
32
|
+
alias == equals?
|
33
|
+
|
34
|
+
def filter(predicate = nil, &block)
|
35
|
+
callable = from_callable_class(predicate, block, J8::Predicate)
|
36
|
+
|
37
|
+
if present? && callable.test(@value)
|
38
|
+
J8::Optional.new(@value)
|
39
|
+
else
|
40
|
+
J8::Optional.empty
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def flat_map(function = nil, &block)
|
45
|
+
callable = from_callable_class(function, block, J8::Function)
|
46
|
+
return J8::Optional.empty unless present?
|
47
|
+
|
48
|
+
callable.apply(@value).tap do |result|
|
49
|
+
raise J8::NilException if result.nil?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def get
|
54
|
+
raise J8::NoSuchElementException unless present?
|
55
|
+
|
56
|
+
@value
|
57
|
+
end
|
58
|
+
alias value get
|
59
|
+
|
60
|
+
def if_present(consumer = nil, &block)
|
61
|
+
callable = from_callable_class(consumer, block, J8::Consumer)
|
62
|
+
return unless present?
|
63
|
+
|
64
|
+
callable.accept(@value)
|
65
|
+
end
|
66
|
+
|
67
|
+
def present?
|
68
|
+
!@value.nil?
|
69
|
+
end
|
70
|
+
|
71
|
+
def map(function = nil, &block)
|
72
|
+
callable = from_callable_class(function, block, J8::Function)
|
73
|
+
return J8::Optional.empty unless present?
|
74
|
+
|
75
|
+
J8::Optional.new(callable.apply(@value))
|
76
|
+
end
|
77
|
+
|
78
|
+
def or_else(value)
|
79
|
+
if present?
|
80
|
+
@value
|
81
|
+
else
|
82
|
+
value
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def or_else_get(supplier = nil, &block)
|
87
|
+
callable = from_callable_class(supplier, block, J8::Supplier)
|
88
|
+
|
89
|
+
if present?
|
90
|
+
@value
|
91
|
+
else
|
92
|
+
callable.get.tap do |result|
|
93
|
+
raise J8::NilException if result.nil?
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def or_else_raise(supplier = nil, &block)
|
99
|
+
callable = from_callable_class(supplier, block, J8::Supplier)
|
100
|
+
raise callable.get unless present?
|
101
|
+
|
102
|
+
@value
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|