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.
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'j8/functional'
@@ -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
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module J8
4
+ class Collector
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module J8
4
+ class Collectors
5
+ end
6
+ end
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module J8
4
+ class NilException < StandardError; end
5
+
6
+ class NoSuchElementException < StandardError; end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module J8
4
+ module Functional
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -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