mayak 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb1722eed3175033b058ab2e87589e45d31fb701f402f51978758663d0ad3c82
4
- data.tar.gz: 3b2aa6f45f79aa848bd017e208f4568aef6ecdc9f3f80fb18eae8b5053fee551
3
+ metadata.gz: 7c803fe79f01fbe1d57d090db09b47e5e8500656819f61951e120fd10860680e
4
+ data.tar.gz: 8dcaeaa4cbf92d629113c7faedaa1048dad5a69aaca38e82fbadd8609631be3b
5
5
  SHA512:
6
- metadata.gz: 9ea0826e78b1cf81e74fae2df74d42f3842e19fa05c5b9bfcdd95955221efb8cb161bcd72dd8623e55defd8b1b1677745edea195b9bf4c5c644eadb575f47d92
7
- data.tar.gz: d20145430d27b269422d1c18a81256704969535c7a9616ad92da9008a4fb3ca63a46c5d24b103473232ab5a3de513f91b455d77213a75ff06c54df93d91f4b1f
6
+ metadata.gz: 144fb12c3b37c4cd8f8abdcca91042178647273ee26f8abc23bf3f83373ad5e04affda9aa1a58331fc619f0c362156b57f5b6d5b66bde15bfd5b1c5b966ef042
7
+ data.tar.gz: e50fe76a28b4591a0ee5b3de40da4b88bff7a05303d7673e00d9873b6abd41a6d6b94d0367660d61bd2777e2d3871f399a83b9f3fbcb1ad9bbaa97b868021184
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
- # Mayak
1
+ [gem]: https://rubygems.org/gems/mayak
2
+ [actions]: https://github.com/dnvkv/mayak/actions
3
+
4
+ # Mayak [![Gem Version](https://badge.fury.io/rb/mayak.svg)][gem] [![CI Status](https://github.com/dnvkv/mayak/actions/workflows/ci.yml/badge.svg)][actions]
2
5
 
3
6
  ### Overview
4
7
 
@@ -38,234 +41,11 @@ Mayak consists from separate classes and interfaces as well as separate modules
38
41
 
39
42
  [Documentation](./lib/mayak/http/README.md)
40
43
 
41
- #### Miscellaneous
42
-
43
- ##### Lazy
44
-
45
- `Lazy` classs represents a value that evaluates only during it's access, and evaluates only once
46
- during the first access. Basically, `Lazy` wraps a block of code (thunk) that returns a value (`Lazy` has single type parameter of a value), and executes only when the value accessed for the first time
47
- and then stores it afterward.
48
-
49
- In order to build `Lazy` a type parameter of value holded should be provided as well as a block that computes a value of the type.
50
- Note that the block is not executed right away.
51
-
52
- ```ruby
53
- lazy1 = ::Mayak::Lazy[Integer].new { 1 }
54
-
55
- buffer = []
56
- lazy2 = ::Mayak::Lazy[Integer].new do
57
- buffer << 1
58
- 1
59
- end
60
- buffer
61
- #> []
62
- ```
63
-
64
- To access the value call `#value`. If the value is not yet computed, provided block will be executed and its result will be stored. Further invokations
65
- of this method won't execute the block again.
66
-
67
- ```ruby
68
- buffer = []
69
- lazy = ::Mayak::Lazy[Integer].new do
70
- buffer << 1
71
- 1
72
- end
73
- buffer
74
- #> []
75
-
76
- # Will execute the block and return the computed value.
77
- lazy.value
78
- #> 1
79
- buffer
80
- #> [1]
81
-
82
- # Will return the memoized value, but won't call the block again
83
- lazy.value
84
- #> 1
85
- buffer
86
- #> [1]
87
- ```
88
-
89
- `Lazy` can be used in situations, when we want to inject some dependency into some class or method, but it may not be used, and the computation or aacquisition of the dependency may be cosftul. In this cases, it's acquisitation may be wrapped in lazy.
90
-
91
- In more imperative style
92
- ```ruby
93
- sig { params(env_variable: String, file_content: ::Mayak::Lazy[String], default: String).returns(String) }
94
- def fetch_config(env_variable, file_content, default)
95
- from_environment = ENV[env_variable]
96
- if env.empty?
97
- file_config = ::Core::Json.parse(file_content.value).success_or({})
98
- from_file = file_config["configuration"]
99
- if from_file.empty?
100
- default
101
- else
102
- from_file
103
- end
104
- else
105
- from_environment
106
- end
107
- end
108
- ```
109
-
110
- Using Mayak monads:
111
- ```ruby
112
- include ::Mayak::Monads::Maybe::Mixin
113
-
114
- sig { params(env_variable: String, file_content: ::Mayak::Lazy[String], default: String).returns(String) }
115
- def fetch_config(env_variable, file_content, default)
116
- Maybe(ENV[env_variable])
117
- .recover_with_maybe(::Core::Json.parse(file_content.value).to_maybe)
118
- .flat_map { |json| Maybe(json["configuration"]) }
119
- .value_or(default)
120
- end
121
- ```
122
-
123
- This method receives name of environment variable, and file content as lazy value. The method
124
- tries to read the environment variable, and if it's not present and reads the file content to find the configuration.
125
- `Lazy` allows to incapsulate behaviour of reading from file, so it can be passed as dependency, method `#fetch_config` doesn't
126
- know anything about reading from file, but because of usage of `lazy` we can postpone it's execution thus avoiding unnecessary work.
127
-
128
- `Lazy` can be transformed via methods `#map` and `#flat_map`.
129
-
130
- Method `#map` allows to transform value inside `Lazy` without triggering executing. Note that `#map` returns
131
- a new instance without mutating previous `Lazy`.
132
- ```ruby
133
- int_lazy = ::Mayak::Lazy[Integer].new do
134
- puts("On initialize")
135
- 1
136
- end
137
- string_lazy = int_lazy.map do |int|
138
- puts("On mapping")
139
- int.to_s
140
- end
141
- int_lazy.value # 1
142
- #> On initialize
143
-
144
- string_lazy.value # "1"
145
- #> On initialize
146
- #> On mapping
147
-
148
- sig { params(file_content: ::Mayak::Lazy[String]).returns(::Mayak::Lazy[Maybe[String]]) }
149
- def file_content_config(file_content)
150
- file_content.map do |file_content|
151
- ::Core::Json
152
- .parse(file_content.value)
153
- .to_maybe
154
- .flat_map { |json| Maybe(json["configuration"]) }
155
- end
156
- end
157
- ```
158
-
159
- Method `#flat_map` allows to chain lazy computations. It receives a block, that builds a new `Lazy` value from the value of original
160
- `Lazy` and returns a new instance of `Lazy`.
161
-
162
- ```ruby
163
- sig { params(env_name: String).returns(::Mayak::Lazy[String]) }
164
- def lazy_env(env_name)
165
- ::Mayak::Lazy[String].new { ENV[env_name] }
166
- end
167
-
168
- env_variable_name = ::Mayak::Lazy[String].new { "VARIABLE" }
169
- env_variable = env_variable_name.flat_map { |env_name| lazy_env(env_name) }
170
- ```
171
-
172
- This may be useful when want to perform a lazy computation based on result of some other lazy computation without enforcing the evaluation.
173
-
174
- For example we have a file that contains list of file names. We can build a lazy computation that read all lines from this code.
175
- ```ruby
176
- sig { params(file_name: String).returns(::Mayak::Lazy[T::Array[String]]) }
177
- def read_file_lines(file_name)
178
- ::Mayak::Lazy[T::Array[String]].new { File.read(file_name).split }
179
- end
180
- ```
181
-
182
- Let's we want to read all filenames from the root file, and then read the first file lazily. In this cases, the lazy computation can be chained via `#flat_map`:
183
-
184
- ```ruby
185
- sig { params(file_name: String).returns(::Mayak::Lazy[T::Array[String]]) }
186
- def read_first_file(file_name)
187
- read_file_lines(file_name).flat_map do |file_names|
188
- Maybe(file_names.first)
189
- .filter(&:empty?)
190
- .map { |file| read_file_lines(file) }
191
- .value_or(::Mayak::Lazy[T::Array[String]].new { [] })
192
- end
193
- end
194
- ```
195
-
196
- In order to combine two lazies of different types into a single one, method `#combine` can be used.
197
- This method receives another lazy (it can be lazy of different type), and a block
198
- and returns a lazy containing result of applying passed blocked to values calculated by lazies.
199
-
200
- ```ruby
201
- class ConfigFiles < T::Struct
202
- const :database_config_file, ::File
203
- const :server_config_file, ::File
204
- end
205
-
206
- sig { returns(::Mayak::Lazy[File]) }
207
- def database_config_file
208
- ::Mayak::Lazy[File].new { File.new(DATABASE_CONFIG_FILE_NAME, "r") }
209
- end
210
-
211
- sig { returns(::Mayak::Lazy[File]) }
212
- def server_config_file
213
- ::Mayak::Lazy[File].new { File.new(SERVER_CONFIG_FILE_NAME, "r") }
214
- end
215
-
216
- sig { returns(::Mayak::Lazy[ConfigFiles]) }
217
- def config_files
218
- database_config_file.combine(server_config_file) do |db_file, server_file|
219
- ConfigFiles.new(
220
- database_config_file: database_config_file,
221
- server_config_file: server_file
222
- )
223
- end
224
- end
225
- ```
226
-
227
- The same behaviour can be achieved with a method `.combine_two`:
228
-
229
- ```ruby
230
- sig { returns(::Mayak::Lazy[ConfigFiles]) }
231
- def config_files
232
- ::Mayak::Lazy.combine_two(database_config_file, server_config_file) do |db_file, server_file|
233
- ConfigFiles.new(
234
- database_config_file: database_config_file,
235
- server_config_file: server_file
236
- )
237
- end
238
- end
239
- ```
240
-
241
- There are also methods `.combine_three`, `.combine_four` upto `.combine_sevel` to combine multiple lazies of diffent types.
44
+ #### Lazy
242
45
 
243
- If you need to combined multiple lazies containing the same value, you can use `.combine_many`. It works
244
- as `Array#reduce`: receives an array of lazies containing the same type, initial value of result type, and a block
245
- receiving accumulator value of result type, and value of next lazy.
46
+ [Documentation](./lib/mayak/lazy/README.md)
246
47
 
247
- ```ruby
248
- sig { returns(::Mayak::Lazy[Integer]) }
249
- def lazy
250
- ::Mayak::Lazy.combine_many(
251
- [::Mayak::Lazy[Integer].new(1), ::Mayak::Lazy[Integer].new(2), ::Mayak::Lazy[Integer].new(3)],
252
- 0
253
- ) { |acc, value| acc + value }
254
- end
255
-
256
- lazy.value # 10
257
- ```
258
-
259
- If you need to transform array of lazies of some value into lazy of array of the value, you can use `.sequence` method.
260
-
261
- ```ruby
262
- sig { returns(::Mayak::Lazy[T::Array[Integer]]) }
263
- def lazy
264
- ::Mayak::Lazy.sequence([::Mayak::Lazy[Integer].new(1), ::Mayak::Lazy[Integer].new(2), ::Mayak::Lazy[Integer].new(3)])
265
- end
266
-
267
- lazy.value # [1, 2, 3]
268
- ```
48
+ #### Miscellaneous
269
49
 
270
50
  ##### Function
271
51
  In some situations Sorbet can not infer a type of proc passed:
@@ -0,0 +1,70 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ module Mayak
5
+ module Codec
6
+ extend T::Sig
7
+ extend T::Generic
8
+ extend T::Helpers
9
+
10
+ abstract!
11
+
12
+ Entity = type_member
13
+ Protocol = type_member
14
+
15
+ sig { abstract.params(entity: Entity).returns(Protocol) }
16
+ def encode(entity)
17
+ end
18
+
19
+ sig {
20
+ abstract
21
+ .params(response: Protocol)
22
+ .returns(Mayak::Monads::Try[Entity])
23
+ }
24
+ def decode(response)
25
+ end
26
+
27
+ sig { returns(::Mayak::Encoder[Entity, Protocol]) }
28
+ def to_encoder
29
+ ::Mayak::Encoder::Implementation[Entity, Protocol].new do |entity|
30
+ encode(entity)
31
+ end
32
+ end
33
+
34
+ sig { returns(::Mayak::Decoder[Protocol, Entity]) }
35
+ def to_decoder
36
+ ::Mayak::Decoder::Implementation[Protocol, Entity].new do |protocol|
37
+ decode(protocol)
38
+ end
39
+ end
40
+
41
+ class FromPair < T::Struct
42
+ extend T::Sig
43
+ extend T::Generic
44
+ extend T::Helpers
45
+
46
+ Entity = type_member
47
+ Protocol = type_member
48
+
49
+ include ::Mayak::Codec
50
+
51
+ const :encoder, ::Mayak::Encoder[Entity, Protocol]
52
+ const :decoder, ::Mayak::Decoder[Protocol, Entity]
53
+
54
+
55
+ sig { override.params(entity: Entity).returns(Protocol) }
56
+ def encode(entity)
57
+ encoder.encode(entity)
58
+ end
59
+
60
+ sig {
61
+ override
62
+ .params(response: Protocol)
63
+ .returns(Mayak::Monads::Try[Entity])
64
+ }
65
+ def decode(response)
66
+ decoder.decode(response)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,15 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Mayak
5
+ module Csv
6
+ class Body < T::Struct
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ Value = type_member
11
+
12
+ const :rows, T::Array[Row[Value]]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Mayak
5
+ module Csv
6
+ module Codec
7
+ extend T::Sig
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Mayak
5
+ module Csv
6
+ class Column < T::Struct
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ Value = type_member
11
+
12
+ const :name, String
13
+ const :serializer, T.proc.params(value: Value).returns(String)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,154 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Mayak
5
+ module Csv
6
+ module Decoder
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ Value = type_member
11
+
12
+ abstract!
13
+
14
+ sig {
15
+ abstract
16
+ .params(csv: T.any(String, T::Enumerable[String]))
17
+ .returns(Mayak::Monads::Try[T::Array[Value]])
18
+ }
19
+ def decode(csv)
20
+ end
21
+
22
+ sig {
23
+ type_parameters(:NewValue)
24
+ .params(blk: T.proc.params(arg: Value).returns(T.type_parameter(:NewValue)))
25
+ .returns(::Mayak::Csv::Decoder[T.type_parameter(:NewValue)])
26
+ }
27
+ def map(&blk)
28
+ ::Mayak::Csv::Decoder::FromFunction[T.type_parameter(:NewValue)].new(fn: -> (csv) do
29
+ decode(csv).map do |values|
30
+ values.map { |value| blk.call(value) }
31
+ end
32
+ end)
33
+ end
34
+
35
+ sig {
36
+ type_parameters(:NewValue)
37
+ .params(blk: T.proc.params(arg: Value).returns(Mayak::Monads::Try[T.type_parameter(:NewValue)]))
38
+ .returns(::Mayak::Csv::Decoder[T.type_parameter(:NewValue)])
39
+ }
40
+ def map_try(&blk)
41
+ ::Mayak::Csv::Decoder::FromFunction[T.type_parameter(:NewValue)].new(fn: -> (csv) do
42
+ decode(csv).flat_map do |values|
43
+ Mayak::Monads::Try.sequence(
44
+ values.map { |value| blk.call(value) }
45
+ )
46
+ end
47
+ end)
48
+ end
49
+
50
+ sig { params(separator: String).returns(Mayak::Csv::Decoder[T::Hash[String, String]]) }
51
+ def self.hash_decoder_strict(separator: ",")
52
+ HashDecoderStrict.new(separator: separator)
53
+ end
54
+
55
+ sig { params(separator: String).returns(Mayak::Csv::Decoder[T::Hash[String, T.nilable(String)]]) }
56
+ def self.hash_decoder(separator: ",")
57
+ HashDecoder.new(separator: separator)
58
+ end
59
+
60
+ class HashDecoder < T::Struct
61
+ extend T::Sig
62
+ extend T::Generic
63
+
64
+ Value = type_member {{ fixed: T::Hash[String, T.nilable(String)] }}
65
+
66
+ const :separator, String, default: ","
67
+
68
+ include ::Mayak::Csv::Decoder
69
+
70
+ sig {
71
+ override
72
+ .params(csv: T.any(String, T::Enumerable[String]))
73
+ .returns(Mayak::Monads::Try[T::Array[Value]])
74
+ }
75
+ def decode(csv)
76
+ csv_string = begin
77
+ case csv
78
+ when String then csv
79
+ else csv.to_a.join("\n")
80
+ end
81
+ end
82
+ Mayak::Monads::Try::Success.new(CSV.parse(csv_string, headers: :first_row, col_sep: separator).map(&:to_h))
83
+ end
84
+ end
85
+
86
+ class HashDecoderStrict < T::Struct
87
+ extend T::Sig
88
+ extend T::Generic
89
+
90
+ Value = type_member {{ fixed: T::Hash[String, String] }}
91
+
92
+ const :separator, String, default: ","
93
+
94
+ include ::Mayak::Csv::Decoder
95
+
96
+ sig {
97
+ override
98
+ .params(csv: T.any(String, T::Enumerable[String]))
99
+ .returns(Mayak::Monads::Try[T::Array[Value]])
100
+ }
101
+ def decode(csv)
102
+ lines = begin
103
+ case csv
104
+ when String then csv.split("\n")
105
+ else csv.to_a
106
+ end
107
+ end
108
+ header, *rows = lines
109
+ return Mayak::Monads::Try::Success.new([]) if rows.nil? || rows.empty?
110
+ return Mayak::Monads::Try::Success.new([]) if header.nil? || header.empty?
111
+ keys = header.split(separator)
112
+ parse_results = rows.map.with_index do |row, index|
113
+ values = row.split(separator)
114
+ if values.length != keys.length
115
+ Mayak::Monads::Try::Failure.new(
116
+ ::Mayak::Csv::ParseError.new(build_error_message(keys.length, row.length, index))
117
+ )
118
+ else
119
+ keys.zip(values).to_h
120
+ end
121
+ end
122
+ Mayak::Monads::Try.sequence(parse_results).map(&:to_h)
123
+ end
124
+
125
+ private
126
+
127
+ sig { params(keys_length: Integer, rows_length: Integer, index: Integer).returns(String) }
128
+ def build_error_message(keys_length, rows_length, index)
129
+ "Invalid number of columns on line #{index + 2}: expected #{keys_length}, found #{rows_length}"
130
+ end
131
+ end
132
+
133
+ class FromFunction < T::Struct
134
+ extend T::Sig
135
+ extend T::Generic
136
+
137
+ Value = type_member
138
+
139
+ include ::Mayak::Csv::Decoder
140
+
141
+ const :fn, T.proc.params(csv: T.any(String, T::Enumerable[String])).returns(Mayak::Monads::Try[T::Array[Value]])
142
+
143
+ sig {
144
+ override
145
+ .params(csv: T.any(String, T::Enumerable[String]))
146
+ .returns(Mayak::Monads::Try[T::Array[Value]])
147
+ }
148
+ def decode(csv)
149
+ fn.call(csv)
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,28 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Mayak
5
+ module Csv
6
+ class Document < T::Struct
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ Value = type_member
11
+
12
+ const :header, Header[Value]
13
+ const :body, Body[Value]
14
+
15
+ sig { params(separator: String).returns(String) }
16
+ def serialize_to_csv(separator: ",")
17
+ buffer = String.new
18
+ buffer << header.serialize_to_csv(separator: separator)
19
+ buffer << "\n"
20
+ body.rows.each do |row|
21
+ buffer << row.serialize_to_csv(separator: separator)
22
+ buffer << "\n"
23
+ end
24
+ buffer
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Mayak
5
+ module Csv
6
+ module Encoder
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ Value = type_member
11
+
12
+ abstract!
13
+
14
+ sig { abstract.params(values: T::Enumerable[Value]).returns(String) }
15
+ def encode(values)
16
+ end
17
+
18
+ class FromFunction < T::Struct
19
+ extend T::Sig
20
+ extend T::Generic
21
+
22
+ Value = type_member
23
+
24
+ include ::Mayak::Csv::Encoder
25
+
26
+ const :fn, T.proc.params(value: T::Enumerable[Value]).returns(String)
27
+
28
+ sig { override.params(values: T::Enumerable[Value]).returns(String) }
29
+ def encode(values)
30
+ fn.call(values)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,54 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Mayak
5
+ module Csv
6
+ class Header < T::Struct
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ Value = type_member
11
+
12
+ const :columns, T::Array[Column[Value]]
13
+
14
+ sig {
15
+ params(
16
+ name: String,
17
+ serializer: T.proc.params(value: Value).returns(String)
18
+ ).returns(::Mayak::Csv::Header[Value])
19
+ }
20
+ def with_column(name, &serializer)
21
+ new_column = ::Mayak::Csv::Column[Value].new(
22
+ name: name,
23
+ serializer: -> (value) { serializer.call(value) }
24
+ )
25
+ Header[Value].new(columns: columns.concat([new_column]))
26
+ end
27
+
28
+ sig { params(values: T::Enumerable[Value]).returns(Body[Value]) }
29
+ def build_body(values)
30
+ rows = values.map do |value|
31
+ Row.new(cells: columns.map { |column| [column, column.serializer.call(value)] })
32
+ end
33
+ Body.new(rows: rows)
34
+ end
35
+
36
+ sig { params(values: T::Enumerable[Value]).returns(Document[Value]) }
37
+ def build_document(values)
38
+ Document[Value].new(header: self, body: build_body(values))
39
+ end
40
+
41
+ sig { params(separator: String).returns(String) }
42
+ def serialize_to_csv(separator: ",")
43
+ columns.map(&:name).join(separator)
44
+ end
45
+
46
+ sig { params(separator: String).returns(::Mayak::Csv::Encoder[Value]) }
47
+ def to_encoder(separator: ",")
48
+ ::Mayak::Csv::Encoder::FromFunction[Value].new(
49
+ fn: -> (values) { build_document(values).serialize_to_csv(separator: separator) }
50
+ )
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module Mayak
5
+ module Csv
6
+ class ParseError < StandardError
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Mayak
5
+ module Csv
6
+ class Row < T::Struct
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ Value = type_member
11
+
12
+ const :cells, T::Array[[Column[Value], String]]
13
+
14
+ sig { params(separator: String).returns(String) }
15
+ def serialize_to_csv(separator: ",")
16
+ cells.map { |cell| cell[1] }.join(separator)
17
+ end
18
+ end
19
+ end
20
+ end
data/lib/mayak/decoder.rb CHANGED
@@ -9,15 +9,51 @@ module Mayak
9
9
 
10
10
  abstract!
11
11
 
12
- RequestType = type_member
13
- RequestEntity = type_member
12
+ In = type_member(:in)
13
+ Out = type_member(:out)
14
14
 
15
15
  sig {
16
16
  abstract
17
- .params(response: RequestType)
18
- .returns(Mayak::Monads::Try[RequestEntity])
17
+ .params(response: In)
18
+ .returns(Mayak::Monads::Try[Out])
19
19
  }
20
20
  def decode(response)
21
21
  end
22
+
23
+ sig {
24
+ type_parameters(:In2)
25
+ .params(blk: T.proc.params(arg0: Out).returns(T.type_parameter(:In2)))
26
+ .returns(::Mayak::Decoder[In, T.type_parameter(:In2)])
27
+ }
28
+ def map(&blk)
29
+ ::Mayak::Decoder::Implementation[In, T.type_parameter(:In2)].new do |entity|
30
+ decode(entity).map { |result| blk.call(result) }
31
+ end
32
+ end
33
+
34
+ class Implementation
35
+ extend T::Sig
36
+ extend T::Generic
37
+ extend T::Helpers
38
+
39
+ include ::Mayak::Decoder
40
+
41
+ In = type_member
42
+ Out = type_member
43
+
44
+ sig { params(function: T.proc.params(response: In).returns(::Mayak::Monads::Try[Out])).void }
45
+ def initialize(&function)
46
+ @function = T.let(function, T.proc.params(response: In).returns(::Mayak::Monads::Try[Out]))
47
+ end
48
+
49
+ sig {
50
+ override
51
+ .params(response: In)
52
+ .returns(Mayak::Monads::Try[Out])
53
+ }
54
+ def decode(response)
55
+ @function.call(response)
56
+ end
57
+ end
22
58
  end
23
59
  end
data/lib/mayak/encoder.rb CHANGED
@@ -9,40 +9,40 @@ module Mayak
9
9
 
10
10
  abstract!
11
11
 
12
- ResponseEntity = type_member
13
- ResponseType = type_member
12
+ In = type_member(:in)
13
+ Out = type_member(:out)
14
14
 
15
- sig { abstract.params(entity: ResponseEntity).returns(ResponseType) }
16
- def encode(entity)
15
+ sig { abstract.params(input: In).returns(Out) }
16
+ def encode(input)
17
17
  end
18
18
 
19
19
  sig {
20
- type_parameters(:NewResponse)
21
- .params(blk: T.proc.params(arg0: ResponseType).returns(T.type_parameter(:NewResponse)))
22
- .returns(::Mayak::Encoder[ResponseEntity, T.type_parameter(:NewResponse)])
20
+ type_parameters(:In2)
21
+ .params(blk: T.proc.params(arg0: Out).returns(T.type_parameter(:In2)))
22
+ .returns(::Mayak::Encoder[In, T.type_parameter(:In2)])
23
23
  }
24
- def map_request(&blk)
25
- ::Mayak::Encoder::FromFunction[ResponseEntity, T.type_parameter(:NewResponse)].new do |entity|
24
+ def map(&blk)
25
+ ::Mayak::Encoder::Implementation[In, T.type_parameter(:In2)].new do |entity|
26
26
  blk.call(encode(entity))
27
27
  end
28
28
  end
29
29
 
30
- class FromFunction
30
+ class Implementation
31
31
  extend T::Sig
32
32
  extend T::Generic
33
33
  extend T::Helpers
34
34
 
35
35
  include ::Mayak::Encoder
36
36
 
37
- ResponseEntity = type_member
38
- ResponseType = type_member
37
+ In = type_member
38
+ Out = type_member
39
39
 
40
- sig { params(function: T.proc.params(response: ResponseEntity).returns(ResponseType)).void }
40
+ sig { params(function: T.proc.params(in: In).returns(Out)).void }
41
41
  def initialize(&function)
42
- @function = T.let(function, T.proc.params(response: ResponseEntity).returns(ResponseType))
42
+ @function = T.let(function, T.proc.params(in: In).returns(Out))
43
43
  end
44
44
 
45
- sig { override.params(entity: ResponseEntity).returns(ResponseType) }
45
+ sig { override.params(entity: In).returns(Out) }
46
46
  def encode(entity)
47
47
  @function.call(entity)
48
48
  end
@@ -14,8 +14,8 @@ module Mayak
14
14
 
15
15
  include ::Mayak::Decoder
16
16
 
17
- RequestType = type_member {{ fixed: ::Mayak::Http::Request }}
18
- RequestEntity = type_member
17
+ In = type_member(:in) {{ fixed: ::Mayak::Http::Request }}
18
+ Out = type_member(:out)
19
19
 
20
20
  sig {
21
21
  type_parameters(:A)
@@ -33,13 +33,13 @@ module Mayak
33
33
 
34
34
  include ::Mayak::Http::Decoder
35
35
 
36
- RequestType = type_member {{ fixed: ::Mayak::Http::Request }}
37
- RequestEntity = type_member {{ fixed: ::Mayak::Http::Request }}
36
+ In = type_member(:in) {{ fixed: ::Mayak::Http::Request }}
37
+ Out = type_member(:out) {{ fixed: ::Mayak::Http::Request }}
38
38
 
39
39
  sig {
40
40
  override
41
- .params(response: RequestType)
42
- .returns(Mayak::Monads::Try[RequestEntity])
41
+ .params(response: In)
42
+ .returns(Mayak::Monads::Try[Out])
43
43
  }
44
44
  def decode(response)
45
45
  Mayak::Monads::Try::Success.new(response)
@@ -53,15 +53,15 @@ module Mayak
53
53
 
54
54
  include ::Mayak::Http::Decoder
55
55
 
56
- RequestType = type_member {{ fixed: ::Mayak::Http::Request }}
57
- RequestEntity = type_member
56
+ In = type_member {{ fixed: ::Mayak::Http::Request }}
57
+ Out = type_member
58
58
 
59
- const :decoder, T.proc.params(arg: String).returns(Mayak::Monads::Try[RequestEntity])
59
+ const :decoder, T.proc.params(arg: String).returns(Mayak::Monads::Try[Out])
60
60
 
61
61
  sig {
62
62
  override
63
- .params(response: RequestType)
64
- .returns(Mayak::Monads::Try[RequestEntity])
63
+ .params(response: In)
64
+ .returns(Mayak::Monads::Try[Out])
65
65
  }
66
66
  def decode(response)
67
67
  decoder.call(response.body || "")
@@ -12,8 +12,8 @@ module Mayak
12
12
 
13
13
  include ::Mayak::Encoder
14
14
 
15
- ResponseEntity = type_member
16
- ResponseType = type_member {{ fixed: Mayak::Http::Response }}
15
+ In = type_member
16
+ Out = type_member {{ fixed: Mayak::Http::Response }}
17
17
 
18
18
  class IdentityEncoder
19
19
  extend T::Sig
@@ -22,36 +22,15 @@ module Mayak
22
22
 
23
23
  include ::Mayak::Http::Encoder
24
24
 
25
- ResponseEntity = type_member {{ fixed: ::Mayak::Http::Response }}
26
- ResponseType = type_member {{ fixed: Mayak::Http::Response }}
25
+ In = type_member {{ fixed: ::Mayak::Http::Response }}
26
+ Out = type_member {{ fixed: Mayak::Http::Response }}
27
27
 
28
- sig { override.params(entity: ResponseEntity).returns(ResponseType) }
28
+ sig { override.params(entity: In).returns(Out) }
29
29
  def encode(entity)
30
30
  entity
31
31
  end
32
32
  end
33
33
 
34
- class FromFunction
35
- extend T::Sig
36
- extend T::Generic
37
- extend T::Helpers
38
-
39
- include ::Mayak::Http::Encoder
40
-
41
- ResponseEntity = type_member
42
- ResponseType = type_member {{ fixed: Mayak::Http::Response }}
43
-
44
- sig { params(function: T.proc.params(response: ResponseEntity).returns(ResponseType)).void }
45
- def initialize(&function)
46
- @function = T.let(function, T.proc.params(response: ResponseEntity).returns(ResponseType))
47
- end
48
-
49
- sig { override.params(entity: ResponseEntity).returns(ResponseType) }
50
- def encode(entity)
51
- @function.call(entity)
52
- end
53
- end
54
-
55
34
  class FromHashSerializableJson < T::Struct
56
35
  extend T::Sig
57
36
  extend T::Generic
@@ -62,10 +41,10 @@ module Mayak
62
41
  const :default_status, Integer
63
42
  const :default_headers, T::Hash[String, String]
64
43
 
65
- ResponseEntity = type_member {{ fixed: ::Mayak::HashSerializable }}
66
- ResponseType = type_member {{ fixed: Mayak::Http::Response }}
44
+ In = type_member {{ fixed: ::Mayak::HashSerializable }}
45
+ Out = type_member {{ fixed: Mayak::Http::Response }}
67
46
 
68
- sig { override.params(entity: ResponseEntity).returns(ResponseType) }
47
+ sig { override.params(entity: In).returns(Out) }
69
48
  def encode(entity)
70
49
  Mayak::Http::Response.new(
71
50
  status: default_status,
@@ -14,29 +14,8 @@ module Mayak
14
14
 
15
15
  include ::Mayak::Encoder
16
16
 
17
- ResponseEntity = type_member
18
- ResponseType = type_member {{ fixed: ::Mayak::Json::JsonType }}
19
-
20
- class FromFunction
21
- extend T::Sig
22
- extend T::Generic
23
- extend T::Helpers
24
-
25
- include ::Mayak::Encoder
26
-
27
- ResponseEntity = type_member
28
- ResponseType = type_member {{ fixed: ::Mayak::Json::JsonType }}
29
-
30
- sig { params(function: T.proc.params(response: ResponseEntity).returns(ResponseType)).void }
31
- def initialize(&function)
32
- @function = T.let(function, T.proc.params(response: ResponseEntity).returns(ResponseType))
33
- end
34
-
35
- sig { override.params(entity: ResponseEntity).returns(ResponseType) }
36
- def encode(entity)
37
- @function.call(entity)
38
- end
39
- end
17
+ In = type_member
18
+ Out = type_member {{ fixed: ::Mayak::Json::JsonType }}
40
19
  end
41
20
  end
42
21
  end
@@ -0,0 +1,226 @@
1
+ # Lazy
2
+
3
+ `Lazy` classs represents a value that evaluates only during it's access, and evaluates only once
4
+ during the first access. Basically, `Lazy` wraps a block of code (thunk) that returns a value (`Lazy` has single type parameter of a value), and executes only when the value accessed for the first time
5
+ and then stores it afterward.
6
+
7
+ In order to build `Lazy` a type parameter of value holded should be provided as well as a block that computes a value of the type.
8
+ Note that the block is not executed right away.
9
+
10
+ ```ruby
11
+ lazy1 = ::Mayak::Lazy[Integer].new { 1 }
12
+
13
+ buffer = []
14
+ lazy2 = ::Mayak::Lazy[Integer].new do
15
+ buffer << 1
16
+ 1
17
+ end
18
+ buffer
19
+ #> []
20
+ ```
21
+
22
+ To access the value call `#value`. If the value is not yet computed, provided block will be executed and its result will be stored. Further invokations
23
+ of this method won't execute the block again.
24
+
25
+ ```ruby
26
+ buffer = []
27
+ lazy = ::Mayak::Lazy[Integer].new do
28
+ buffer << 1
29
+ 1
30
+ end
31
+ buffer
32
+ #> []
33
+
34
+ # Will execute the block and return the computed value.
35
+ lazy.value
36
+ #> 1
37
+ buffer
38
+ #> [1]
39
+
40
+ # Will return the memoized value, but won't call the block again
41
+ lazy.value
42
+ #> 1
43
+ buffer
44
+ #> [1]
45
+ ```
46
+
47
+ `Lazy` can be used in situations, when we want to inject some dependency into some class or method, but it may not be used, and the computation or aacquisition of the dependency may be cosftul. In this cases, it's acquisitation may be wrapped in lazy.
48
+
49
+ In more imperative style
50
+ ```ruby
51
+ sig { params(env_variable: String, file_content: ::Mayak::Lazy[String], default: String).returns(String) }
52
+ def fetch_config(env_variable, file_content, default)
53
+ from_environment = ENV[env_variable]
54
+ if env.empty?
55
+ file_config = ::Core::Json.parse(file_content.value).success_or({})
56
+ from_file = file_config["configuration"]
57
+ if from_file.empty?
58
+ default
59
+ else
60
+ from_file
61
+ end
62
+ else
63
+ from_environment
64
+ end
65
+ end
66
+ ```
67
+
68
+ Using Mayak monads:
69
+ ```ruby
70
+ include ::Mayak::Monads::Maybe::Mixin
71
+
72
+ sig { params(env_variable: String, file_content: ::Mayak::Lazy[String], default: String).returns(String) }
73
+ def fetch_config(env_variable, file_content, default)
74
+ Maybe(ENV[env_variable])
75
+ .recover_with_maybe(::Core::Json.parse(file_content.value).to_maybe)
76
+ .flat_map { |json| Maybe(json["configuration"]) }
77
+ .value_or(default)
78
+ end
79
+ ```
80
+
81
+ This method receives name of environment variable, and file content as lazy value. The method
82
+ tries to read the environment variable, and if it's not present and reads the file content to find the configuration.
83
+ `Lazy` allows to incapsulate behaviour of reading from file, so it can be passed as dependency, method `#fetch_config` doesn't
84
+ know anything about reading from file, but because of usage of `lazy` we can postpone it's execution thus avoiding unnecessary work.
85
+
86
+ `Lazy` can be transformed via methods `#map` and `#flat_map`.
87
+
88
+ Method `#map` allows to transform value inside `Lazy` without triggering executing. Note that `#map` returns
89
+ a new instance without mutating previous `Lazy`.
90
+ ```ruby
91
+ int_lazy = ::Mayak::Lazy[Integer].new do
92
+ puts("On initialize")
93
+ 1
94
+ end
95
+ string_lazy = int_lazy.map do |int|
96
+ puts("On mapping")
97
+ int.to_s
98
+ end
99
+ int_lazy.value # 1
100
+ #> On initialize
101
+
102
+ string_lazy.value # "1"
103
+ #> On initialize
104
+ #> On mapping
105
+
106
+ sig { params(file_content: ::Mayak::Lazy[String]).returns(::Mayak::Lazy[Maybe[String]]) }
107
+ def file_content_config(file_content)
108
+ file_content.map do |file_content|
109
+ ::Core::Json
110
+ .parse(file_content.value)
111
+ .to_maybe
112
+ .flat_map { |json| Maybe(json["configuration"]) }
113
+ end
114
+ end
115
+ ```
116
+
117
+ Method `#flat_map` allows to chain lazy computations. It receives a block, that builds a new `Lazy` value from the value of original
118
+ `Lazy` and returns a new instance of `Lazy`.
119
+
120
+ ```ruby
121
+ sig { params(env_name: String).returns(::Mayak::Lazy[String]) }
122
+ def lazy_env(env_name)
123
+ ::Mayak::Lazy[String].new { ENV[env_name] }
124
+ end
125
+
126
+ env_variable_name = ::Mayak::Lazy[String].new { "VARIABLE" }
127
+ env_variable = env_variable_name.flat_map { |env_name| lazy_env(env_name) }
128
+ ```
129
+
130
+ This may be useful when want to perform a lazy computation based on result of some other lazy computation without enforcing the evaluation.
131
+
132
+ For example we have a file that contains list of file names. We can build a lazy computation that read all lines from this code.
133
+ ```ruby
134
+ sig { params(file_name: String).returns(::Mayak::Lazy[T::Array[String]]) }
135
+ def read_file_lines(file_name)
136
+ ::Mayak::Lazy[T::Array[String]].new { File.read(file_name).split }
137
+ end
138
+ ```
139
+
140
+ Let's we want to read all filenames from the root file, and then read the first file lazily. In this cases, the lazy computation can be chained via `#flat_map`:
141
+
142
+ ```ruby
143
+ sig { params(file_name: String).returns(::Mayak::Lazy[T::Array[String]]) }
144
+ def read_first_file(file_name)
145
+ read_file_lines(file_name).flat_map do |file_names|
146
+ Maybe(file_names.first)
147
+ .filter(&:empty?)
148
+ .map { |file| read_file_lines(file) }
149
+ .value_or(::Mayak::Lazy[T::Array[String]].new { [] })
150
+ end
151
+ end
152
+ ```
153
+
154
+ In order to combine two lazies of different types into a single one, method `#combine` can be used.
155
+ This method receives another lazy (it can be lazy of different type), and a block
156
+ and returns a lazy containing result of applying passed blocked to values calculated by lazies.
157
+
158
+ ```ruby
159
+ class ConfigFiles < T::Struct
160
+ const :database_config_file, ::File
161
+ const :server_config_file, ::File
162
+ end
163
+
164
+ sig { returns(::Mayak::Lazy[File]) }
165
+ def database_config_file
166
+ ::Mayak::Lazy[File].new { File.new(DATABASE_CONFIG_FILE_NAME, "r") }
167
+ end
168
+
169
+ sig { returns(::Mayak::Lazy[File]) }
170
+ def server_config_file
171
+ ::Mayak::Lazy[File].new { File.new(SERVER_CONFIG_FILE_NAME, "r") }
172
+ end
173
+
174
+ sig { returns(::Mayak::Lazy[ConfigFiles]) }
175
+ def config_files
176
+ database_config_file.combine(server_config_file) do |db_file, server_file|
177
+ ConfigFiles.new(
178
+ database_config_file: database_config_file,
179
+ server_config_file: server_file
180
+ )
181
+ end
182
+ end
183
+ ```
184
+
185
+ The same behaviour can be achieved with a method `.combine_two`:
186
+
187
+ ```ruby
188
+ sig { returns(::Mayak::Lazy[ConfigFiles]) }
189
+ def config_files
190
+ ::Mayak::Lazy.combine_two(database_config_file, server_config_file) do |db_file, server_file|
191
+ ConfigFiles.new(
192
+ database_config_file: database_config_file,
193
+ server_config_file: server_file
194
+ )
195
+ end
196
+ end
197
+ ```
198
+
199
+ There are also methods `.combine_three`, `.combine_four` upto `.combine_sevel` to combine multiple lazies of diffent types.
200
+
201
+ If you need to combined multiple lazies containing the same value, you can use `.combine_many`. It works
202
+ as `Array#reduce`: receives an array of lazies containing the same type, initial value of result type, and a block
203
+ receiving accumulator value of result type, and value of next lazy.
204
+
205
+ ```ruby
206
+ sig { returns(::Mayak::Lazy[Integer]) }
207
+ def lazy
208
+ ::Mayak::Lazy.combine_many(
209
+ [::Mayak::Lazy[Integer].new(1), ::Mayak::Lazy[Integer].new(2), ::Mayak::Lazy[Integer].new(3)],
210
+ 0
211
+ ) { |acc, value| acc + value }
212
+ end
213
+
214
+ lazy.value # 10
215
+ ```
216
+
217
+ If you need to transform array of lazies of some value into lazy of array of the value, you can use `.sequence` method.
218
+
219
+ ```ruby
220
+ sig { returns(::Mayak::Lazy[T::Array[Integer]]) }
221
+ def lazy
222
+ ::Mayak::Lazy.sequence([::Mayak::Lazy[Integer].new(1), ::Mayak::Lazy[Integer].new(2), ::Mayak::Lazy[Integer].new(3)])
223
+ end
224
+
225
+ lazy.value # [1, 2, 3]
226
+ ```
data/lib/mayak.rb CHANGED
@@ -30,7 +30,6 @@ require_relative 'mayak/http/request'
30
30
  require_relative 'mayak/http/response'
31
31
  require_relative 'mayak/http/verb'
32
32
  require_relative 'mayak/http/client'
33
- require_relative 'mayak/http/codec'
34
33
 
35
34
  require_relative 'mayak/monads/maybe'
36
35
  require_relative 'mayak/monads/result'
data/mayak.gemspec CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "mayak"
7
- spec.version = "0.1.0"
7
+ spec.version = "0.2.0"
8
8
  spec.summary = "Set of fully typed utility classes and interfaces integrated with Sorbet."
9
9
  spec.description = spec.summary
10
10
  spec.authors = ["Daniil Bober"]
@@ -18,6 +18,5 @@ Gem::Specification.new do |spec|
18
18
 
19
19
  spec.add_development_dependency "bundler"
20
20
  spec.add_development_dependency "rspec"
21
- spec.add_development_dependency "parlour"
22
21
  spec.add_development_dependency "tapioca"
23
22
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mayak
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniil Bober
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-26 00:00:00.000000000 Z
11
+ date: 2025-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sorbet-runtime
@@ -66,20 +66,6 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: parlour
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: tapioca
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -107,9 +93,19 @@ files:
107
93
  - lib/mayak/caching/README.md
108
94
  - lib/mayak/caching/lru_cache.rb
109
95
  - lib/mayak/caching/unbounded_cache.rb
96
+ - lib/mayak/codec.rb
110
97
  - lib/mayak/collections/README.md
111
98
  - lib/mayak/collections/priority_queue.rb
112
99
  - lib/mayak/collections/queue.rb
100
+ - lib/mayak/csv/body.rb
101
+ - lib/mayak/csv/codec.rb
102
+ - lib/mayak/csv/column.rb
103
+ - lib/mayak/csv/decoder.rb
104
+ - lib/mayak/csv/document.rb
105
+ - lib/mayak/csv/encoder.rb
106
+ - lib/mayak/csv/header.rb
107
+ - lib/mayak/csv/parse_error.rb
108
+ - lib/mayak/csv/row.rb
113
109
  - lib/mayak/decoder.rb
114
110
  - lib/mayak/encoder.rb
115
111
  - lib/mayak/failable_function.rb
@@ -117,7 +113,6 @@ files:
117
113
  - lib/mayak/hash_serializable.rb
118
114
  - lib/mayak/http/README.md
119
115
  - lib/mayak/http/client.rb
120
- - lib/mayak/http/codec.rb
121
116
  - lib/mayak/http/decoder.rb
122
117
  - lib/mayak/http/encoder.rb
123
118
  - lib/mayak/http/request.rb
@@ -126,6 +121,7 @@ files:
126
121
  - lib/mayak/json.rb
127
122
  - lib/mayak/json/encoder.rb
128
123
  - lib/mayak/lazy.rb
124
+ - lib/mayak/lazy/README.md
129
125
  - lib/mayak/monads/README.md
130
126
  - lib/mayak/monads/maybe.rb
131
127
  - lib/mayak/monads/result.rb
@@ -1,25 +0,0 @@
1
- # typed: strong
2
- # frozen_string_literal: true
3
-
4
- require_relative 'encoder'
5
- require_relative 'decoder'
6
-
7
- module Mayak
8
- module Http
9
- module Codec
10
- extend T::Sig
11
- extend T::Generic
12
- extend T::Helpers
13
-
14
- abstract!
15
-
16
- include ::Mayak::Http::Encoder
17
- include ::Mayak::Http::Decoder
18
-
19
- ResponseEntity = type_member
20
- ResponseType = type_member {{ fixed: ::Mayak::Http::Response }}
21
- RequestType = type_member {{ fixed: ::Mayak::Http::Request }}
22
- RequestEntity = type_member
23
- end
24
- end
25
- end