qo 0.5.0 → 0.99.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 +5 -5
- data/.travis.yml +3 -3
- data/README.md +106 -9
- data/Rakefile +3 -3
- data/lib/qo.rb +17 -15
- data/lib/qo/branches/branch.rb +191 -0
- data/lib/qo/branches/branches.rb +37 -0
- data/lib/qo/branches/else_branch.rb +20 -0
- data/lib/qo/branches/error_branch.rb +26 -0
- data/lib/qo/branches/failure_branch.rb +26 -0
- data/lib/qo/branches/monadic_else_branch.rb +26 -0
- data/lib/qo/branches/monadic_when_branch.rb +26 -0
- data/lib/qo/branches/success_branch.rb +26 -0
- data/lib/qo/branches/when_branch.rb +21 -0
- data/lib/qo/destructurers/destructurer.rb +85 -0
- data/lib/qo/destructurers/destructurers.rb +15 -0
- data/lib/qo/exceptions.rb +3 -32
- data/lib/qo/matchers/matcher.rb +298 -0
- data/lib/qo/pattern_matchers/branching.rb +52 -0
- data/lib/qo/pattern_matchers/pattern_match.rb +170 -0
- data/lib/qo/pattern_matchers/pattern_matchers.rb +13 -0
- data/lib/qo/pattern_matchers/result_pattern_match.rb +45 -0
- data/lib/qo/public_api.rb +130 -8
- data/lib/qo/version.rb +1 -1
- data/qo.gemspec +11 -0
- metadata +28 -11
- data/lib/qo/helpers.rb +0 -44
- data/lib/qo/matchers/array_matcher.rb +0 -99
- data/lib/qo/matchers/base_matcher.rb +0 -110
- data/lib/qo/matchers/guard_block_matcher.rb +0 -91
- data/lib/qo/matchers/hash_matcher.rb +0 -144
- data/lib/qo/matchers/pattern_match.rb +0 -126
@@ -0,0 +1,52 @@
|
|
1
|
+
module Qo
|
2
|
+
module PatternMatchers
|
3
|
+
# A module to allow the registration of braches to a pattern matcher.
|
4
|
+
#
|
5
|
+
# @author baweaver
|
6
|
+
# @since 1.0.0
|
7
|
+
#
|
8
|
+
module Branching
|
9
|
+
# On inclusion, extend the class including this module with branch
|
10
|
+
# registration methods.
|
11
|
+
#
|
12
|
+
# @param base [Class]
|
13
|
+
# Class including this module, passed from `include`
|
14
|
+
def self.included(base)
|
15
|
+
base.extend(ClassMethods)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Class methods to extend the including class with
|
19
|
+
#
|
20
|
+
# @author baweaver
|
21
|
+
# @since 1.0.0
|
22
|
+
module ClassMethods
|
23
|
+
# Registers a branch to a pattern matcher.
|
24
|
+
#
|
25
|
+
# This defines a method on the pattern matcher matching the `name` of
|
26
|
+
# the branch. If the name is `where`, the pattern matcher will now be
|
27
|
+
# given a method called `where` with which to match with.
|
28
|
+
#
|
29
|
+
# When called, this will either ammend a matcher to the list of matchers
|
30
|
+
# or set a default matcher if the branch happens to be a default.
|
31
|
+
#
|
32
|
+
# @param branch [Branch]
|
33
|
+
# Branch object to register with a pattern matcher
|
34
|
+
def register_branch(branch)
|
35
|
+
define_method(branch.name) do |*conditions, **keyword_conditions, &function|
|
36
|
+
qo_matcher = Qo::Matchers::Matcher.new('and', conditions, keyword_conditions)
|
37
|
+
|
38
|
+
branch_matcher = branch.create_matcher(
|
39
|
+
qo_matcher, destructure: @destructure, &function
|
40
|
+
)
|
41
|
+
|
42
|
+
if branch.default?
|
43
|
+
@default = branch_matcher
|
44
|
+
else
|
45
|
+
@matchers << branch_matcher
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
module Qo
|
2
|
+
module PatternMatchers
|
3
|
+
# Classic Pattern Match. This matcher uses `when` and `else` branches and
|
4
|
+
# is meant to be a more powerful variant of the case statement
|
5
|
+
#
|
6
|
+
# @author baweaver
|
7
|
+
# @since 0.2.0
|
8
|
+
class PatternMatch
|
9
|
+
include Branching
|
10
|
+
|
11
|
+
# The regular pattern matcher from classic Qo uses `when` and `else`
|
12
|
+
# branches, like a `case` statement
|
13
|
+
register_branch Qo::Branches::WhenBranch.new
|
14
|
+
register_branch Qo::Branches::ElseBranch.new
|
15
|
+
|
16
|
+
# Creates a new instance of a pattern matcher
|
17
|
+
#
|
18
|
+
# @param destructure: false [Boolean]
|
19
|
+
# Whether or not to destructure values before yielding to a block
|
20
|
+
#
|
21
|
+
# @param &fn [Proc]
|
22
|
+
# Function to be used to construct the pattern matcher's branches
|
23
|
+
#
|
24
|
+
# @return [Qo::PatternMatchers::PatternMatch]
|
25
|
+
def initialize(destructure: false, &fn)
|
26
|
+
@matchers = []
|
27
|
+
@default = nil
|
28
|
+
@destructure = destructure
|
29
|
+
|
30
|
+
yield(self) if block_given?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Allows for the creation of an anonymous PatternMatcher based on this
|
34
|
+
# parent class. To be used by people wishing to make their own pattern
|
35
|
+
# matchers with variant branches and other features not included in the
|
36
|
+
# defaultly provided ones
|
37
|
+
#
|
38
|
+
# @param branches: [] [Array[Branch]]
|
39
|
+
# Branches to be used with this new pattern matcher
|
40
|
+
#
|
41
|
+
# @return [Class]
|
42
|
+
# Anonymous pattern matcher class to be bound to a constant or used
|
43
|
+
# anonymously.
|
44
|
+
def self.create(branches: [])
|
45
|
+
Class.new(Qo::PatternMatchers::PatternMatch) do
|
46
|
+
branches.each { |branch| register_branch(branch.new) }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Allows for the injection of a pattern matching function into a type class
|
51
|
+
# for direct access, rather than yielding an instance of that class to a
|
52
|
+
# pattern matcher.
|
53
|
+
#
|
54
|
+
# This is typically done for monadic types that need to `match`. When
|
55
|
+
# combined with extractor type branches it can be very handy for dealing
|
56
|
+
# with container types.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
#
|
60
|
+
# ```ruby
|
61
|
+
# # Technically Some and None don't exist yet, so we have to "cheat" instead
|
62
|
+
# # of just saying `Some` for the precondition
|
63
|
+
# SomeBranch = Qo.create_branch(
|
64
|
+
# name: 'some',
|
65
|
+
# precondition: -> v { v.is_a?(Some) },
|
66
|
+
# extractor: :value
|
67
|
+
# )
|
68
|
+
#
|
69
|
+
# NoneBranch = Qo.create_branch(
|
70
|
+
# name: 'none',
|
71
|
+
# precondition: -> v { v.is_a?(None) },
|
72
|
+
# extractor: :value
|
73
|
+
# )
|
74
|
+
#
|
75
|
+
# SomePatternMatch = Qo.create_pattern_match(branches: [SomeBranch, NoneBranch])
|
76
|
+
#
|
77
|
+
# class Some
|
78
|
+
# include SomePatternMatch.mixin
|
79
|
+
#
|
80
|
+
# attr_reader :value
|
81
|
+
#
|
82
|
+
# def initialize(value) @value = value end
|
83
|
+
#
|
84
|
+
# def fmap(&fn)
|
85
|
+
# new_value = fn.call(value)
|
86
|
+
# new_value ? Some.new(new_value) : None(value)
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# class None
|
91
|
+
# include SomePatternMatch.mixin
|
92
|
+
#
|
93
|
+
# attr_reader :value
|
94
|
+
#
|
95
|
+
# def initialize(value) @value = value end
|
96
|
+
# def fmap(&fn) None.new(value) end
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# Some.new(1)
|
100
|
+
# .fmap { |v| v * 2 }
|
101
|
+
# .match { |m|
|
102
|
+
# m.some { |v| v + 100 }
|
103
|
+
# m.none { "OHNO!" }
|
104
|
+
# }
|
105
|
+
# => 102
|
106
|
+
#
|
107
|
+
# Some.new(1)
|
108
|
+
# .fmap { |v| nil }
|
109
|
+
# .match { |m|
|
110
|
+
# m.some { |v| v + 100 }
|
111
|
+
# m.none { "OHNO!" }
|
112
|
+
# }
|
113
|
+
# => "OHNO!"
|
114
|
+
# ```
|
115
|
+
#
|
116
|
+
# @param destructure: false [Boolean]
|
117
|
+
# Whether or not to destructure values before yielding to a block
|
118
|
+
#
|
119
|
+
# @param as: :match [Symbol]
|
120
|
+
# Name to use as a method name bound to the including class
|
121
|
+
#
|
122
|
+
# @return [Module]
|
123
|
+
# Module to be mixed into a class
|
124
|
+
def self.mixin(destructure: false, as: :match)
|
125
|
+
create_self = -> &function { new(destructure: destructure, &function) }
|
126
|
+
|
127
|
+
Module.new do
|
128
|
+
define_method(as) do |&function|
|
129
|
+
create_self.call(&function).call(self)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Calls the pattern matcher, yielding the target value to the first
|
135
|
+
# matching branch it encounters.
|
136
|
+
#
|
137
|
+
# @param value [Any]
|
138
|
+
# Value to match against
|
139
|
+
#
|
140
|
+
# @return [Any]
|
141
|
+
# Result of the called branch
|
142
|
+
#
|
143
|
+
# @return [nil]
|
144
|
+
# Returns nil if no branch is matched
|
145
|
+
def call(value)
|
146
|
+
@matchers.each do |matcher|
|
147
|
+
status, return_value = matcher.call(value)
|
148
|
+
return return_value if status
|
149
|
+
end
|
150
|
+
|
151
|
+
if @default
|
152
|
+
_, return_value = @default.call(value)
|
153
|
+
return_value
|
154
|
+
else
|
155
|
+
nil
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
alias === call
|
160
|
+
alias [] call
|
161
|
+
|
162
|
+
# Procified version of `call`
|
163
|
+
#
|
164
|
+
# @return [Proc[Any] => Any]
|
165
|
+
def to_proc
|
166
|
+
-> target { self.call(target) }
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Qo
|
2
|
+
# Classes for constructing pattern matchers, or defining your own
|
3
|
+
# should you want to.
|
4
|
+
#
|
5
|
+
# @author baweaver
|
6
|
+
# @since 1.0.0
|
7
|
+
module PatternMatchers
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'qo/pattern_matchers/branching'
|
12
|
+
require 'qo/pattern_matchers/pattern_match'
|
13
|
+
require 'qo/pattern_matchers/result_pattern_match'
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Qo
|
2
|
+
module PatternMatchers
|
3
|
+
# Unlike the normal pattern matcher, this one works on tuple arrays containing
|
4
|
+
# a status and a result like `[:ok, result]` and `[:err, message]`.
|
5
|
+
#
|
6
|
+
# Note that each of these can still take conditionals much like a `where`
|
7
|
+
# branch for more fine grained control over what we're looking for.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# ```ruby
|
11
|
+
# matcher = Qo.result_match { |m|
|
12
|
+
# m.success(String) { |v| "Hello #{v}"}
|
13
|
+
# m.success { |v| v + 10 }
|
14
|
+
# m.failure { |v| "Error: #{v}" }
|
15
|
+
# }
|
16
|
+
#
|
17
|
+
# matcher.call([:ok, 'there'])
|
18
|
+
# # => "Hello there"
|
19
|
+
#
|
20
|
+
# matcher.call([:ok, 4])
|
21
|
+
# # => 14
|
22
|
+
#
|
23
|
+
# matcher.call([:err, 'OH NO'])
|
24
|
+
# # => "Error: OH NO"
|
25
|
+
# ```
|
26
|
+
#
|
27
|
+
# @author baweaver
|
28
|
+
# @since 1.0.0
|
29
|
+
class ResultPatternMatch < PatternMatch
|
30
|
+
register_branch Qo::Branches::SuccessBranch.new
|
31
|
+
register_branch Qo::Branches::FailureBranch.new
|
32
|
+
|
33
|
+
# Creates a new result matcher
|
34
|
+
#
|
35
|
+
# @param destructure: false [Boolean]
|
36
|
+
# Whether or not to destructure the value before yielding to
|
37
|
+
# the first matched block
|
38
|
+
#
|
39
|
+
# @return [type] [description]
|
40
|
+
def initialize(destructure: false)
|
41
|
+
super(destructure: destructure)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/qo/public_api.rb
CHANGED
@@ -73,15 +73,19 @@ module Qo
|
|
73
73
|
# })
|
74
74
|
# => [0, 6, 2]
|
75
75
|
#
|
76
|
+
# @param destructure: [Boolean]
|
77
|
+
# Whether or not to destructure an object before yielding it to the
|
78
|
+
# given function.
|
79
|
+
#
|
76
80
|
# @param fn [Proc]
|
77
81
|
# Body of the matcher, as shown in examples
|
78
82
|
#
|
79
83
|
# @return [Qo::PatternMatch]
|
80
84
|
# A function awaiting a value to match against
|
81
|
-
def match(&fn)
|
85
|
+
def match(destructure: false, &fn)
|
82
86
|
return proc { false } unless block_given?
|
83
87
|
|
84
|
-
Qo::
|
88
|
+
Qo::PatternMatchers::PatternMatch.new(destructure: destructure, &fn)
|
85
89
|
end
|
86
90
|
|
87
91
|
# Similar to the common case statement of Ruby, except in that it behaves
|
@@ -105,16 +109,136 @@ module Qo
|
|
105
109
|
# @param value [Any]
|
106
110
|
# Value to match against
|
107
111
|
#
|
112
|
+
# @param destructure: [Boolean]
|
113
|
+
# Whether or not to destructure an object before yielding it to the
|
114
|
+
# given function.
|
115
|
+
#
|
108
116
|
# @param &fn [Proc]
|
109
117
|
# Body of the matcher, as shown above
|
110
118
|
#
|
111
119
|
# @return [Any]
|
112
120
|
# The result of calling a pattern match with a provided value
|
113
|
-
def case(value, &fn)
|
114
|
-
Qo::
|
121
|
+
def case(value, destructure: false, &fn)
|
122
|
+
Qo::PatternMatchers::PatternMatch.new(destructure: destructure, &fn).call(value)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Similar to `match`, except it uses a `ResultPatternMatch` which instead
|
126
|
+
# responds to tuple types:
|
127
|
+
#
|
128
|
+
# @example
|
129
|
+
#
|
130
|
+
# ```ruby
|
131
|
+
# pm = Qo.result_match { |m|
|
132
|
+
# m.success { |v| v + 10 }
|
133
|
+
# m.failure { |v| "Error: #{v}" }
|
134
|
+
# }
|
135
|
+
#
|
136
|
+
# pm.call([:ok, 3])
|
137
|
+
# # => 13
|
138
|
+
#
|
139
|
+
# pm.call([:err, "No Good"])
|
140
|
+
# # => "Error: No Good"
|
141
|
+
# ```
|
142
|
+
#
|
143
|
+
# @param destructure: [Boolean]
|
144
|
+
# Whether or not to destructure an object before yielding it to the
|
145
|
+
# given function.
|
146
|
+
#
|
147
|
+
# @param &fn [Proc]
|
148
|
+
# Body of the matcher, as shown above
|
149
|
+
#
|
150
|
+
# @see match
|
151
|
+
#
|
152
|
+
# @return [Proc[Any] => Any]
|
153
|
+
# Proc awaiting a value to match against.
|
154
|
+
def result_match(destructure: false, &fn)
|
155
|
+
return proc { false } unless block_given?
|
156
|
+
|
157
|
+
Qo::PatternMatchers::ResultPatternMatch.new(destructure: destructure, &fn)
|
115
158
|
end
|
116
159
|
|
117
|
-
#
|
160
|
+
# Similar to `case`, except it uses a `ResultPatternMatch` instead.
|
161
|
+
#
|
162
|
+
# @see match
|
163
|
+
# @see result_match
|
164
|
+
# @see case
|
165
|
+
#
|
166
|
+
# @param target [Any]
|
167
|
+
# Target to match against
|
168
|
+
#
|
169
|
+
# @param destructure: [Boolean]
|
170
|
+
# Whether or not to destructure an object before yielding it to the
|
171
|
+
# given function.
|
172
|
+
#
|
173
|
+
# @param &fn [Proc]
|
174
|
+
# Body of the matcher, as shown above
|
175
|
+
#
|
176
|
+
# @return [Any]
|
177
|
+
# Result of the match
|
178
|
+
def result_case(target, destructure: false, &fn)
|
179
|
+
Qo::PatternMatchers::ResultPatternMatch
|
180
|
+
.new(destructure: destructure, &fn)
|
181
|
+
.call(target)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Dynamically creates a new branch to be used with custom pattern matchers.
|
185
|
+
#
|
186
|
+
# @param name: [String]
|
187
|
+
# Name of the branch. This is what binds to the pattern match as a method,
|
188
|
+
# meaning a name of `where` will result in calling it as `m.where`.
|
189
|
+
#
|
190
|
+
# @param precondition: Any [Symbol, #===]
|
191
|
+
# A precondition to the branch being considered true. This is done for
|
192
|
+
# static conditions like a certain type or perhaps checking a tuple type
|
193
|
+
# like `[:ok, value]`.
|
194
|
+
#
|
195
|
+
# If a `Symbol` is given, Qo will coerce it into a proc. This is done to
|
196
|
+
# make a nicer shorthand for creating a branch.
|
197
|
+
#
|
198
|
+
# @param extractor: IDENTITY [Proc, Symbol]
|
199
|
+
# How to pull the value out of a target object when a branch matches before
|
200
|
+
# calling the associated function. For a monadic type this might be something
|
201
|
+
# like extracting the value before yielding to the given block.
|
202
|
+
#
|
203
|
+
# If a `Symbol` is given, Qo will coerce it into a proc. This is done to
|
204
|
+
# make a nicer shorthand for creating a branch.
|
205
|
+
#
|
206
|
+
# @param destructure: false
|
207
|
+
# Whether or not to destructure the given object before yielding to the
|
208
|
+
# associated block. This means that the given block now places great
|
209
|
+
# importance on the argument names, as they'll be used to extract values
|
210
|
+
# from the associated object by that same method name, or key name in the
|
211
|
+
# case of hashes.
|
212
|
+
#
|
213
|
+
# @param default: false [Boolean]
|
214
|
+
# Whether this branch is considered to be a default condition. This is
|
215
|
+
# done to ensure that a branch runs last after all other conditions have
|
216
|
+
# failed. An example of this would be an `else` branch.
|
217
|
+
#
|
218
|
+
# @return [Class]
|
219
|
+
# Anonymous branch class to be bound to a constant or used directly
|
220
|
+
def create_branch(name:, precondition: Any, extractor: IDENTITY, destructure: false, default: false)
|
221
|
+
Qo::Branches::Branch.create(
|
222
|
+
name: name,
|
223
|
+
precondition: precondition,
|
224
|
+
extractor: extractor,
|
225
|
+
destructure: destructure,
|
226
|
+
default: default
|
227
|
+
)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Creates a new type of pattern matcher from a set of branches
|
231
|
+
#
|
232
|
+
# @param branches: [Array[Branch]]
|
233
|
+
# An array of branches that this pattern matcher will respond to
|
234
|
+
#
|
235
|
+
# @return [Class]
|
236
|
+
# Anonymous pattern matcher to be bound to a constant or used directly
|
237
|
+
def create_pattern_match(branches:)
|
238
|
+
Qo::PatternMatchers::PatternMatch.create(branches: branches)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Abstraction for creating a matcher.
|
118
242
|
#
|
119
243
|
# @param type [String]
|
120
244
|
# Type of matcher
|
@@ -127,9 +251,7 @@ module Qo
|
|
127
251
|
#
|
128
252
|
# @return [Qo::Matcher]
|
129
253
|
private def create_matcher(type, array_matchers, keyword_matchers)
|
130
|
-
|
131
|
-
|
132
|
-
Qo::Matchers::BaseMatcher.new(type, array_matchers, keyword_matchers)
|
254
|
+
Qo::Matchers::Matcher.new(type, array_matchers, keyword_matchers)
|
133
255
|
end
|
134
256
|
end
|
135
257
|
end
|