fear 1.0.0 → 1.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 +4 -4
- data/.rubocop.yml +4 -60
- data/.travis.yml +8 -4
- data/CHANGELOG.md +7 -1
- data/Gemfile +5 -3
- data/Gemfile.lock +18 -20
- data/README.md +28 -14
- data/Rakefile +61 -60
- data/examples/pattern_extracting.rb +8 -6
- data/examples/pattern_matching_binary_tree_set.rb +4 -2
- data/examples/pattern_matching_number_in_words.rb +46 -42
- data/fear.gemspec +29 -27
- data/lib/fear.rb +44 -37
- data/lib/fear/await.rb +33 -0
- data/lib/fear/awaitable.rb +28 -0
- data/lib/fear/either.rb +2 -0
- data/lib/fear/either_api.rb +2 -0
- data/lib/fear/either_pattern_match.rb +2 -0
- data/lib/fear/empty_partial_function.rb +3 -1
- data/lib/fear/extractor.rb +30 -28
- data/lib/fear/extractor/anonymous_array_splat_matcher.rb +2 -0
- data/lib/fear/extractor/any_matcher.rb +2 -0
- data/lib/fear/extractor/array_head_matcher.rb +2 -0
- data/lib/fear/extractor/array_matcher.rb +2 -0
- data/lib/fear/extractor/array_splat_matcher.rb +2 -0
- data/lib/fear/extractor/empty_list_matcher.rb +2 -0
- data/lib/fear/extractor/extractor_matcher.rb +5 -3
- data/lib/fear/extractor/grammar.rb +5 -3
- data/lib/fear/extractor/identifier_matcher.rb +2 -0
- data/lib/fear/extractor/matcher.rb +5 -3
- data/lib/fear/extractor/matcher/and.rb +3 -1
- data/lib/fear/extractor/named_array_splat_matcher.rb +2 -0
- data/lib/fear/extractor/pattern.rb +7 -5
- data/lib/fear/extractor/typed_identifier_matcher.rb +2 -0
- data/lib/fear/extractor/value_matcher.rb +2 -0
- data/lib/fear/extractor_api.rb +2 -0
- data/lib/fear/failure.rb +2 -0
- data/lib/fear/failure_pattern_match.rb +2 -0
- data/lib/fear/for.rb +4 -2
- data/lib/fear/for_api.rb +3 -1
- data/lib/fear/future.rb +141 -64
- data/lib/fear/future_api.rb +2 -0
- data/lib/fear/left.rb +3 -1
- data/lib/fear/left_pattern_match.rb +2 -0
- data/lib/fear/none.rb +4 -2
- data/lib/fear/none_pattern_match.rb +2 -0
- data/lib/fear/option.rb +3 -1
- data/lib/fear/option_api.rb +2 -0
- data/lib/fear/option_pattern_match.rb +2 -0
- data/lib/fear/partial_function.rb +10 -8
- data/lib/fear/partial_function/and_then.rb +4 -2
- data/lib/fear/partial_function/any.rb +2 -0
- data/lib/fear/partial_function/combined.rb +3 -1
- data/lib/fear/partial_function/empty.rb +2 -0
- data/lib/fear/partial_function/guard.rb +7 -5
- data/lib/fear/partial_function/guard/and.rb +2 -0
- data/lib/fear/partial_function/guard/and3.rb +2 -0
- data/lib/fear/partial_function/guard/or.rb +2 -0
- data/lib/fear/partial_function/lifted.rb +2 -0
- data/lib/fear/partial_function/or_else.rb +3 -1
- data/lib/fear/partial_function_class.rb +3 -1
- data/lib/fear/pattern_match.rb +3 -1
- data/lib/fear/pattern_matching_api.rb +3 -1
- data/lib/fear/promise.rb +11 -3
- data/lib/fear/right.rb +3 -1
- data/lib/fear/right_biased.rb +4 -2
- data/lib/fear/right_pattern_match.rb +2 -0
- data/lib/fear/some.rb +2 -0
- data/lib/fear/some_pattern_match.rb +2 -0
- data/lib/fear/struct.rb +235 -0
- data/lib/fear/success.rb +2 -0
- data/lib/fear/success_pattern_match.rb +2 -0
- data/lib/fear/try.rb +2 -0
- data/lib/fear/try_api.rb +2 -0
- data/lib/fear/try_pattern_match.rb +2 -0
- data/lib/fear/unit.rb +6 -2
- data/lib/fear/utils.rb +4 -2
- data/lib/fear/version.rb +4 -1
- data/spec/fear/done_spec.rb +7 -5
- data/spec/fear/either/mixin_spec.rb +4 -2
- data/spec/fear/either_pattern_match_spec.rb +10 -8
- data/spec/fear/extractor/array_matcher_spec.rb +65 -63
- data/spec/fear/extractor/extractor_matcher_spec.rb +64 -62
- data/spec/fear/extractor/grammar_array_spec.rb +5 -3
- data/spec/fear/extractor/identified_matcher_spec.rb +18 -16
- data/spec/fear/extractor/identifier_matcher_spec.rb +26 -24
- data/spec/fear/extractor/pattern_spec.rb +17 -15
- data/spec/fear/extractor/typed_identifier_matcher_spec.rb +23 -21
- data/spec/fear/extractor/value_matcher_number_spec.rb +29 -27
- data/spec/fear/extractor/value_matcher_string_spec.rb +27 -25
- data/spec/fear/extractor/value_matcher_symbol_spec.rb +24 -22
- data/spec/fear/extractor_api_spec.rb +70 -68
- data/spec/fear/extractor_spec.rb +23 -21
- data/spec/fear/failure_spec.rb +59 -57
- data/spec/fear/for_spec.rb +19 -17
- data/spec/fear/future_spec.rb +456 -240
- data/spec/fear/guard_spec.rb +26 -24
- data/spec/fear/left_spec.rb +60 -58
- data/spec/fear/none_spec.rb +36 -34
- data/spec/fear/option/mixin_spec.rb +9 -7
- data/spec/fear/option_pattern_match_spec.rb +10 -8
- data/spec/fear/partial_function/empty_spec.rb +12 -10
- data/spec/fear/partial_function_and_then_spec.rb +39 -37
- data/spec/fear/partial_function_composition_spec.rb +46 -44
- data/spec/fear/partial_function_or_else_spec.rb +92 -90
- data/spec/fear/partial_function_spec.rb +46 -44
- data/spec/fear/pattern_match_spec.rb +31 -29
- data/spec/fear/promise_spec.rb +19 -17
- data/spec/fear/right_biased/left.rb +28 -26
- data/spec/fear/right_biased/right.rb +51 -49
- data/spec/fear/right_spec.rb +58 -56
- data/spec/fear/some_spec.rb +30 -28
- data/spec/fear/success_spec.rb +50 -48
- data/spec/fear/try/mixin_spec.rb +5 -3
- data/spec/fear/try_pattern_match_spec.rb +10 -8
- data/spec/fear/utils_spec.rb +16 -14
- data/spec/spec_helper.rb +7 -5
- data/spec/struct_spec.rb +226 -0
- metadata +18 -13
data/lib/fear/future_api.rb
CHANGED
data/lib/fear/left.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
class Left
|
3
5
|
include Either
|
@@ -52,7 +54,7 @@ module Fear
|
|
52
54
|
# @param reduce_left [Proc]
|
53
55
|
# @return [any]
|
54
56
|
def reduce(reduce_left, _reduce_right)
|
55
|
-
reduce_left.
|
57
|
+
reduce_left.(value)
|
56
58
|
end
|
57
59
|
|
58
60
|
# @return [self]
|
data/lib/fear/none.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
# @api private
|
3
5
|
class NoneClass
|
@@ -40,7 +42,7 @@ module Fear
|
|
40
42
|
|
41
43
|
# @return [String]
|
42
44
|
def inspect
|
43
|
-
|
45
|
+
"#<Fear::NoneClass>"
|
44
46
|
end
|
45
47
|
|
46
48
|
# @return [String]
|
@@ -70,7 +72,7 @@ module Fear
|
|
70
72
|
end
|
71
73
|
|
72
74
|
def inherited(*)
|
73
|
-
raise
|
75
|
+
raise "you are not allowed to inherit from NoneClass, use Fear::None instead"
|
74
76
|
end
|
75
77
|
end
|
76
78
|
end
|
data/lib/fear/option.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
# Represents optional values. Instances of +Option+
|
3
5
|
# are either an instance of +Some+ or the object +None+.
|
@@ -187,7 +189,7 @@ module Fear
|
|
187
189
|
end
|
188
190
|
|
189
191
|
def match(value, &block)
|
190
|
-
matcher(&block).
|
192
|
+
matcher(&block).(value)
|
191
193
|
end
|
192
194
|
end
|
193
195
|
|
data/lib/fear/option_api.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
# A partial function is a unary function defined on subset of all possible inputs.
|
3
5
|
# The method +defined_at?+ allows to test dynamically if an arg is in
|
@@ -43,13 +45,13 @@ module Fear
|
|
43
45
|
# @return [#call]
|
44
46
|
# @abstract
|
45
47
|
module PartialFunction
|
46
|
-
autoload :AndThen,
|
47
|
-
autoload :Any,
|
48
|
-
autoload :Combined,
|
49
|
-
autoload :EMPTY,
|
50
|
-
autoload :Guard,
|
51
|
-
autoload :Lifted,
|
52
|
-
autoload :OrElse,
|
48
|
+
autoload :AndThen, "fear/partial_function/and_then"
|
49
|
+
autoload :Any, "fear/partial_function/any"
|
50
|
+
autoload :Combined, "fear/partial_function/combined"
|
51
|
+
autoload :EMPTY, "fear/partial_function/empty"
|
52
|
+
autoload :Guard, "fear/partial_function/guard"
|
53
|
+
autoload :Lifted, "fear/partial_function/lifted"
|
54
|
+
autoload :OrElse, "fear/partial_function/or_else"
|
53
55
|
|
54
56
|
# Checks if a value is contained in the function's domain.
|
55
57
|
#
|
@@ -125,7 +127,7 @@ module Fear
|
|
125
127
|
# @return [Fear::PartialFunction]
|
126
128
|
#
|
127
129
|
def and_then(other = Utils::UNDEFINED, &block)
|
128
|
-
Utils.with_block_or_argument(
|
130
|
+
Utils.with_block_or_argument("Fear::PartialFunction#and_then", other, block) do |fun|
|
129
131
|
if fun.is_a?(Fear::PartialFunction)
|
130
132
|
Combined.new(self, fun)
|
131
133
|
else
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
module PartialFunction
|
3
5
|
# Composite function produced by +PartialFunction#and_then+ method
|
@@ -23,7 +25,7 @@ module Fear
|
|
23
25
|
# @param arg [any]
|
24
26
|
# @return [any ]
|
25
27
|
def call(arg)
|
26
|
-
function.
|
28
|
+
function.(partial_function.(arg))
|
27
29
|
end
|
28
30
|
|
29
31
|
# @param arg [any]
|
@@ -39,7 +41,7 @@ module Fear
|
|
39
41
|
result = partial_function.call_or_else(arg) do
|
40
42
|
return yield(arg)
|
41
43
|
end
|
42
|
-
function.
|
44
|
+
function.(result)
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
module PartialFunction
|
3
5
|
# Composite function produced by +PartialFunction#and_then+ method
|
@@ -22,7 +24,7 @@ module Fear
|
|
22
24
|
# @param arg [any]
|
23
25
|
# @return [any ]
|
24
26
|
def call(arg)
|
25
|
-
f2.
|
27
|
+
f2.(f1.(arg))
|
26
28
|
end
|
27
29
|
|
28
30
|
alias === call
|
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
module PartialFunction
|
3
5
|
# Guard represents PartialFunction guardian
|
4
6
|
#
|
5
7
|
# @api private
|
6
8
|
class Guard
|
7
|
-
autoload :And,
|
8
|
-
autoload :And3,
|
9
|
-
autoload :Or,
|
9
|
+
autoload :And, "fear/partial_function/guard/and"
|
10
|
+
autoload :And3, "fear/partial_function/guard/and3"
|
11
|
+
autoload :Or, "fear/partial_function/guard/or"
|
10
12
|
|
11
13
|
class << self
|
12
14
|
# Optimized version for combination of two guardians
|
@@ -35,7 +37,7 @@ module Fear
|
|
35
37
|
when 0 then Any
|
36
38
|
else
|
37
39
|
head, *tail = conditions
|
38
|
-
tail.
|
40
|
+
tail.reduce(new(head)) { |acc, condition| acc.and(new(condition)) }
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
@@ -45,7 +47,7 @@ module Fear
|
|
45
47
|
return Any if conditions.empty?
|
46
48
|
|
47
49
|
head, *tail = conditions
|
48
|
-
tail.
|
50
|
+
tail.reduce(new(head)) { |acc, condition| acc.or(new(condition)) }
|
49
51
|
end
|
50
52
|
end
|
51
53
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
module PartialFunction
|
3
5
|
# Composite function produced by +PartialFunction#or_else+ method
|
@@ -36,7 +38,7 @@ module Fear
|
|
36
38
|
|
37
39
|
# @see Fear::PartialFunction#and_then
|
38
40
|
def and_then(other = Utils::UNDEFINED, &block)
|
39
|
-
Utils.with_block_or_argument(
|
41
|
+
Utils.with_block_or_argument("Fear::PartialFunction::OrElse#and_then", other, block) do |fun|
|
40
42
|
OrElse.new(f1.and_then(&fun), f2.and_then(&fun))
|
41
43
|
end
|
42
44
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
# @api private
|
3
5
|
class PartialFunctionClass
|
@@ -25,7 +27,7 @@ module Fear
|
|
25
27
|
# @yield [arg] if function not defined
|
26
28
|
def call_or_else(arg)
|
27
29
|
if defined_at?(arg)
|
28
|
-
function.
|
30
|
+
function.(arg)
|
29
31
|
else
|
30
32
|
yield arg
|
31
33
|
end
|
data/lib/fear/pattern_match.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
# Pattern match builder. Provides DSL for building pattern matcher
|
3
5
|
# Pattern match is just a combination of partial functions
|
@@ -59,7 +61,7 @@ module Fear
|
|
59
61
|
|
60
62
|
Module.new do
|
61
63
|
define_method(as) do |&matchers|
|
62
|
-
matcher_class.new(&matchers).
|
64
|
+
matcher_class.new(&matchers).(self)
|
63
65
|
end
|
64
66
|
end
|
65
67
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
# @api private
|
3
5
|
module PatternMatchingApi
|
@@ -83,7 +85,7 @@ module Fear
|
|
83
85
|
# @yieldparam matcher [Fear::PartialFunction]
|
84
86
|
# @return [any]
|
85
87
|
def match(value, &block)
|
86
|
-
matcher(&block).
|
88
|
+
matcher(&block).(value)
|
87
89
|
end
|
88
90
|
|
89
91
|
# Creates partial function defined on domain described with guards
|
data/lib/fear/promise.rb
CHANGED
@@ -1,8 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "concurrent"
|
5
|
+
rescue LoadError
|
6
|
+
puts "You must add 'concurrent-ruby' to your Gemfile in order to use Fear::Future"
|
7
|
+
end
|
8
|
+
|
1
9
|
module Fear
|
2
10
|
# @api private
|
3
11
|
class Promise < Concurrent::IVar
|
4
|
-
# @param options [Hash] options passed to underlying +Concurrent::
|
5
|
-
def initialize(options
|
12
|
+
# @param options [Hash] options passed to underlying +Concurrent::Promise+
|
13
|
+
def initialize(**options)
|
6
14
|
super()
|
7
15
|
@options = options
|
8
16
|
@promise = Concurrent::Promise.new(options) do
|
@@ -64,7 +72,7 @@ module Fear
|
|
64
72
|
if complete(result)
|
65
73
|
self
|
66
74
|
else
|
67
|
-
raise IllegalStateException,
|
75
|
+
raise IllegalStateException, "Promise already completed."
|
68
76
|
end
|
69
77
|
end
|
70
78
|
|
data/lib/fear/right.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
class Right
|
3
5
|
include Either
|
@@ -65,7 +67,7 @@ module Fear
|
|
65
67
|
# @param reduce_right [Proc]
|
66
68
|
# @return [any]
|
67
69
|
def reduce(_reduce_left, reduce_right)
|
68
|
-
reduce_right.
|
70
|
+
reduce_right.(value)
|
69
71
|
end
|
70
72
|
|
71
73
|
# @return [Either]
|
data/lib/fear/right_biased.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fear
|
2
4
|
# @private
|
3
5
|
module RightBiased
|
@@ -7,14 +9,14 @@ module Fear
|
|
7
9
|
# Returns the value from this `RightBiased::Right` or the given argument if
|
8
10
|
# this is a `RightBiased::Left`.
|
9
11
|
def get_or_else(*args, &block)
|
10
|
-
Utils.assert_arg_or_block!(
|
12
|
+
Utils.assert_arg_or_block!("get_or_else", *args, &block)
|
11
13
|
super
|
12
14
|
end
|
13
15
|
|
14
16
|
# Returns this `RightBiased::Right` or the given alternative if
|
15
17
|
# this is a `RightBiased::Left`.
|
16
18
|
def or_else(*args, &block)
|
17
|
-
Utils.assert_arg_or_block!(
|
19
|
+
Utils.assert_arg_or_block!("or_else", *args, &block)
|
18
20
|
super.tap do |result|
|
19
21
|
Utils.assert_type!(result, left_class, right_class)
|
20
22
|
end
|
data/lib/fear/some.rb
CHANGED
data/lib/fear/struct.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fear
|
4
|
+
# Structs are like regular classes and good for modeling immutable data.
|
5
|
+
#
|
6
|
+
# A minimal struct requires just a list of attributes:
|
7
|
+
#
|
8
|
+
# User = Fear::Struct.with_attributes(:id, :email, :admin)
|
9
|
+
# john = User.new(id: 2, email: 'john@example.com', admin: false)
|
10
|
+
#
|
11
|
+
# john.email #=> 'john@example.com'
|
12
|
+
#
|
13
|
+
# Instead of `.with_attributes` factory method you can use classic inheritance:
|
14
|
+
#
|
15
|
+
# class User < Fear::Struct
|
16
|
+
# attribute :id
|
17
|
+
# attribute :email
|
18
|
+
# attribute :admin
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Since structs are immutable, you are not allowed to reassign their attributes
|
22
|
+
#
|
23
|
+
# john.email = ''john.doe@example.com'' #=> raises NoMethodError
|
24
|
+
#
|
25
|
+
# Two structs of the same type with the same attributes are equal
|
26
|
+
#
|
27
|
+
# john1 = User.new(id: 2, email: 'john@example.com', admin: false)
|
28
|
+
# john2 = User.new(id: 2, admin: false, email: 'john@example.com')
|
29
|
+
# john1 == john2 #=> true
|
30
|
+
#
|
31
|
+
# You can create a shallow copy of a +Struct+ by using copy method optionally changing its attributes.
|
32
|
+
#
|
33
|
+
# john = User.new(id: 2, email: 'john@example.com', admin: false)
|
34
|
+
# admin_john = john.copy(admin: true)
|
35
|
+
#
|
36
|
+
# john.admin #=> false
|
37
|
+
# admin_john.admin #=> true
|
38
|
+
#
|
39
|
+
# It's possible to match against struct attributes. The following example extracts email from
|
40
|
+
# user only if user is admin
|
41
|
+
#
|
42
|
+
# john = User.new(id: 2, email: 'john@example.com', admin: false)
|
43
|
+
# john.match |m|
|
44
|
+
# m.xcase('Fear::Struct(_, email, true)') do |email|
|
45
|
+
# email
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# Note, parameters got extracted in order they was defined.
|
50
|
+
#
|
51
|
+
class Struct
|
52
|
+
include PatternMatch.mixin
|
53
|
+
|
54
|
+
@attributes = [].freeze
|
55
|
+
|
56
|
+
class << self
|
57
|
+
# @param base [Fear::Struct]
|
58
|
+
# @api private
|
59
|
+
def inherited(base)
|
60
|
+
base.instance_variable_set(:@attributes, attributes)
|
61
|
+
Fear.register_extractor(base, Fear.case(base, &:to_a).lift)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Defines attribute
|
65
|
+
#
|
66
|
+
# @param name [Symbol]
|
67
|
+
# @return [Symbol] attribute name
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# class User < Fear::Struct
|
71
|
+
# attribute :id
|
72
|
+
# attribute :email
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
def attribute(name)
|
76
|
+
name.to_sym.tap do |symbolized_name|
|
77
|
+
@attributes << symbolized_name
|
78
|
+
attr_reader symbolized_name
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Members of this struct
|
83
|
+
#
|
84
|
+
# @return [<Symbol>]
|
85
|
+
def attributes
|
86
|
+
@attributes.dup
|
87
|
+
end
|
88
|
+
|
89
|
+
# Creates new struct with given attributes
|
90
|
+
# @param members [<Symbol>]
|
91
|
+
# @return [Fear::Struct]
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# User = Fear::Struct.with_attributes(:id, :email, :admin) do
|
95
|
+
# def admin?
|
96
|
+
# @admin
|
97
|
+
# end
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
def with_attributes(*members, &block)
|
101
|
+
members = members
|
102
|
+
block = block
|
103
|
+
|
104
|
+
Class.new(self) do
|
105
|
+
members.each { |member| attribute(member) }
|
106
|
+
class_eval(&block) if block
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# @param attributes [{Symbol => any}]
|
112
|
+
def initialize(**attributes)
|
113
|
+
_check_missing_attributes!(attributes)
|
114
|
+
_check_unknown_attributes!(attributes)
|
115
|
+
|
116
|
+
@values = members.each_with_object([]) do |name, values|
|
117
|
+
attributes.fetch(name).tap do |value|
|
118
|
+
_set_attribute(name, value)
|
119
|
+
values << value
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Creates a shallow copy of this struct optionally changing the attributes arguments.
|
125
|
+
# @param attributes [{Symbol => any}]
|
126
|
+
#
|
127
|
+
# @example
|
128
|
+
# User = Fear::Struct.new(:id, :email, :admin)
|
129
|
+
# john = User.new(id: 2, email: 'john@example.com', admin: false)
|
130
|
+
# john.admin #=> false
|
131
|
+
# admin_john = john.copy(admin: true)
|
132
|
+
# admin_john.admin #=> true
|
133
|
+
#
|
134
|
+
def copy(**attributes)
|
135
|
+
self.class.new(to_h.merge(attributes))
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns the struct attributes as an array of symbols
|
139
|
+
# @return [<Symbol>]
|
140
|
+
#
|
141
|
+
# @example
|
142
|
+
# User = Fear::Struct.new(:id, :email, :admin)
|
143
|
+
# john = User.new(email: 'john@example.com', admin: false, id: 2)
|
144
|
+
# john.attributes #=> [:id, :email, :admin]
|
145
|
+
#
|
146
|
+
def members
|
147
|
+
self.class.attributes
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns the values for this struct as an Array.
|
151
|
+
# @return [Array]
|
152
|
+
#
|
153
|
+
# @example
|
154
|
+
# User = Fear::Struct.new(:id, :email, :admin)
|
155
|
+
# john = User.new(email: 'john@example.com', admin: false, id: 2)
|
156
|
+
# john.to_a #=> [2, 'john@example.com', false]
|
157
|
+
#
|
158
|
+
def to_a
|
159
|
+
@values.dup
|
160
|
+
end
|
161
|
+
|
162
|
+
# @overload to_h()
|
163
|
+
# Returns a Hash containing the names and values for the struct's attributes
|
164
|
+
# @return [{Symbol => any}]
|
165
|
+
#
|
166
|
+
# @overload to_h(&block)
|
167
|
+
# Applies block to pairs of name name and value and use them to construct hash
|
168
|
+
# @yieldparam pair [<Symbol, any>] yields pair of name name and value
|
169
|
+
# @return [{Symbol => any}]
|
170
|
+
#
|
171
|
+
# @example
|
172
|
+
# User = Fear::Struct.new(:id, :email, :admin)
|
173
|
+
# john = User.new(email: 'john@example.com', admin: false, id: 2)
|
174
|
+
# john.to_h #=> {id: 2, email: 'john@example.com', admin: false}
|
175
|
+
# john.to_h do |key, value|
|
176
|
+
# [key.to_s, value]
|
177
|
+
# end #=> {'id' => 2, 'email' => 'john@example.com', 'admin' => false}
|
178
|
+
#
|
179
|
+
def to_h(&block)
|
180
|
+
pairs = members.zip(@values)
|
181
|
+
if block_given?
|
182
|
+
Hash[pairs.map(&block)]
|
183
|
+
else
|
184
|
+
Hash[pairs]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# @param other [any]
|
189
|
+
# @return [Boolean]
|
190
|
+
def ==(other)
|
191
|
+
other.is_a?(other.class) && to_h == other.to_h
|
192
|
+
end
|
193
|
+
|
194
|
+
INSPECT_TEMPLATE = "<#Fear::Struct %{class_name} %{attributes}>"
|
195
|
+
|
196
|
+
# @return [String]
|
197
|
+
#
|
198
|
+
# @example
|
199
|
+
# User = Fear::Struct.with_attributes(:id, :email)
|
200
|
+
# user = User.new(id: 2, email: 'john@exmaple.com')
|
201
|
+
# user.inspect #=> "<#Fear::Struct User id=2, email=>'john@exmaple.com'>"
|
202
|
+
#
|
203
|
+
def inspect
|
204
|
+
attributes = to_h.map { |key, value| "#{key}=#{value.inspect}" }.join(", ")
|
205
|
+
|
206
|
+
format(INSPECT_TEMPLATE, class_name: self.class.name, attributes: attributes)
|
207
|
+
end
|
208
|
+
alias to_s inspect
|
209
|
+
|
210
|
+
MISSING_KEYWORDS_ERROR = "missing keywords: %{keywords}"
|
211
|
+
|
212
|
+
private def _check_missing_attributes!(provided_attributes)
|
213
|
+
missing_attributes = members - provided_attributes.keys
|
214
|
+
|
215
|
+
unless missing_attributes.empty?
|
216
|
+
raise ArgumentError, format(MISSING_KEYWORDS_ERROR, keywords: missing_attributes.join(", "))
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
UNKNOWN_KEYWORDS_ERROR = "unknown keywords: %{keywords}"
|
221
|
+
|
222
|
+
private def _check_unknown_attributes!(provided_attributes)
|
223
|
+
unknown_attributes = provided_attributes.keys - members
|
224
|
+
|
225
|
+
unless unknown_attributes.empty?
|
226
|
+
raise ArgumentError, format(UNKNOWN_KEYWORDS_ERROR, keywords: unknown_attributes.join(", "))
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# @return [void]
|
231
|
+
private def _set_attribute(name, value)
|
232
|
+
instance_variable_set(:"@#{name}", value)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|