mayak 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c11b0f5c9288055fd9f0ca907a38e257b06b78fdd0f4e1185fafa8beae4fdcc2
4
+ data.tar.gz: f49566c5b0d830610843288c56ae001513a72b34b5beb75a9cb764998c0e6abd
5
+ SHA512:
6
+ metadata.gz: b24805002d39350e5d6fa0f4b8bc501b6c576b34368bc90eca7af794f820f191e7c4e35c1487cb3083ddabd18e2dbb1929822801cd934eebeac298c4bb5d34d4
7
+ data.tar.gz: 87275eb153d98c62d264c20a51db6e99109ec519eb957fbf4728f3b0612686aa41f76f728090d332d72323d16e7a954751fb17ad23b1bf9bff3cf99eba20fef8
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Mayak
2
+
3
+ ### Overview
4
+
5
+ Mayak is a library which aims to provide abstractions for well typed programming in Ruby using Sorbet type checker. Mayak provides generic interfaces and utility classes for various applications, and as a foundation for other libraries.
6
+
7
+ ### Installation
8
+
9
+ In order to use the library, add the following line to your `Gemfile`:
10
+
11
+ ```ruby
12
+ gem 'mayak'
13
+ ```
14
+ or install it via the following command:
15
+ ```ruby
16
+ gem install 'mayak'
17
+ ```
18
+
19
+ ### Documentation
20
+
21
+ Mayak consists from separate classes and interfaces as well as separate modules for specific domains.
22
+
23
+ #### Caching
24
+
25
+ [Documentation](./lib/mayak/caching/README.md)
26
+
27
+ #### Monads
28
+
29
+ [Documentation](./lib/mayak/monads/README.md)
30
+
31
+ #### HTTP
32
+
33
+ [Documentation](./lib/mayak/http/README.md)
@@ -0,0 +1,35 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Mayak
5
+ module Cache
6
+ extend T::Sig
7
+ extend T::Generic
8
+ extend T::Helpers
9
+
10
+ interface!
11
+
12
+ Key = type_member
13
+ Value = type_member
14
+
15
+ sig { abstract.params(key: Key).returns(T.nilable(Value)) }
16
+ def read(key)
17
+ end
18
+
19
+ sig { abstract.params(key: Key, value: Value).void }
20
+ def write(key, value)
21
+ end
22
+
23
+ sig { abstract.params(key: Key, blk: T.proc.returns(Value)).returns(Value) }
24
+ def fetch(key, &blk)
25
+ end
26
+
27
+ sig { abstract.void }
28
+ def clear
29
+ end
30
+
31
+ sig { abstract.params(key: Key).void }
32
+ def delete(key)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,100 @@
1
+ # Caching
2
+
3
+ Caching modules constist from `Mayak::Cache` and several implementations in `Mayak::Caching` module. `Mayak::Caching` provides in-memory caches using regular ruby hashes: an unbounded cache and a cache using LRU eviction policy.
4
+
5
+ Usage of unbounded cache:
6
+ ```ruby
7
+ unbounded_cache = Mayak::Caching::UnboundedCache[String, Integer].new
8
+ unbounded_cache.write("foo", 10)
9
+ unbounded_cache.write("bar", 20)
10
+
11
+ unbounded_cache.read("foo") # 10
12
+ unbounded_cache.read("bar") # 20
13
+ unbounded_cache.read("baz") # nil
14
+
15
+ unbounded_cache.delete("bar")
16
+ unbounded_cache.read("bar") # nil
17
+
18
+ unbounded_cache.fetch("foo") { 100 } # 10
19
+ unbounded_cache.fetch("bar") { 100 } # 100
20
+ unbounded_cache.fetch("bar") { 200 } # 100
21
+
22
+ unbounded_cache.clear # 100
23
+ unbounded_cache.read("foo") # nil
24
+ unbounded_cache.read("bar") # nil
25
+ ```
26
+
27
+ LRU cache has limited size: when the cache is full and a new element is added, some element is evicted using least recently used policy:
28
+
29
+ ```ruby
30
+ lru_cache = Mayak::Caching::LRUCache[String, Integer].new(max_size: 3)
31
+
32
+ lru_cache.write("key1", 1)
33
+ lru_cache.write("key2", 2)
34
+ lru_cache.write("key3", 3)
35
+
36
+ lru_cache.read("key1") # 1
37
+ lru_cache.read("key2") # 2
38
+ lru_cache.read("key3") # 3
39
+
40
+ lru_cache.write("key4", 4)
41
+ lru_cache.read("key4") # 4
42
+ lru_cache.read("key1") # nil
43
+ ```
44
+
45
+ You can implement `Mayak::Cache` interface using a different store (for example default Rails cache) and use different implementations interchangeably:
46
+
47
+ ```ruby
48
+ class RailsCache < T::Struct
49
+ extend T::Sig
50
+ extend T::Generic
51
+ extend T::Helpers
52
+
53
+ include Mayak::Cache
54
+
55
+ Key = type_member
56
+ Value = type_member
57
+
58
+ const :converter, T.proc.params(value: T.untyped).returns(Value)
59
+
60
+ sig { override.params(key: Key).returns(T.nilable(Value)) }
61
+ def read(key)
62
+ converter.call(Rails.cache.read(key))
63
+ end
64
+
65
+ sig { override.params(key: Key, value: Value).void }
66
+ def write(key, value)
67
+ Rails.cache.write(key, value)
68
+ end
69
+
70
+ sig { override.params(key: Key, blk: T.proc.returns(Value)).returns(Value) }
71
+ def fetch(key, &blk)
72
+ converter.call(Rails.cache.fetch(key, &blk))
73
+ end
74
+
75
+ sig { override.void }
76
+ def clear
77
+ Rails.cache.clear
78
+ end
79
+
80
+ sig { override.params(key: Key).void }
81
+ def delete(key)
82
+ Rails.cache.delete(key)
83
+ end
84
+ end
85
+
86
+ class Service < T::Struct
87
+ extend T::Sig
88
+
89
+ const :cache, Mayak::Cache[String, String]
90
+ end
91
+
92
+ in_memory = Service.new(
93
+ cache: Mayak::Caching::UnboundedCache[String, String].new
94
+ )
95
+ rails_cache = Service.new(
96
+ cache: RailsCache[String, String].new(
97
+ converter: -> (value) { value.to_s }
98
+ )
99
+ )
100
+ ```
@@ -0,0 +1,76 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Mayak
5
+ module Caching
6
+ class LRUCache
7
+ extend T::Sig
8
+ extend T::Generic
9
+ extend T::Helpers
10
+
11
+ include Mayak::Cache
12
+
13
+ Key = type_member
14
+ Value = type_member
15
+
16
+ sig { returns(Integer) }
17
+ attr_reader :max_size
18
+
19
+ sig { params(max_size: Integer).void }
20
+ def initialize(max_size:)
21
+ @storage = T.let({}, T::Hash[Key, Value])
22
+ if max_size <= 0
23
+ raise ArgumentError.new("max_size should be a positive integer")
24
+ end
25
+ @max_size = T.let(max_size, Integer)
26
+ end
27
+
28
+ sig { override.params(key: Key).returns(T.nilable(Value)) }
29
+ def read(key)
30
+ @storage[key]
31
+ end
32
+
33
+ sig { override.params(key: Key, value: Value).void }
34
+ def write(key, value)
35
+ if @storage.size == max_size && !@storage.key?(key)
36
+ evict!
37
+ elsif @storage.key?(key)
38
+ @storage.delete(key)
39
+ end
40
+ @storage[key] = value
41
+ end
42
+
43
+ sig { override.params(key: Key, blk: T.proc.returns(Value)).returns(Value) }
44
+ def fetch(key, &blk)
45
+ return T.must(@storage[key]) if @storage.has_key?(key)
46
+
47
+ value = blk.call
48
+ write(key, value)
49
+ value
50
+ end
51
+
52
+ sig { override.void }
53
+ def clear
54
+ @storage.clear
55
+ end
56
+
57
+ sig { override.params(key: Key).void }
58
+ def delete(key)
59
+ @storage.delete(key)
60
+ end
61
+
62
+ private
63
+
64
+ sig { void }
65
+ def evict!
66
+ first_key = @storage.first&.first
67
+ case first_key
68
+ when NilClass
69
+ return
70
+ else
71
+ @storage.delete(first_key)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,51 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Mayak
5
+ module Caching
6
+ class UnboundedCache
7
+ extend T::Sig
8
+ extend T::Generic
9
+ extend T::Helpers
10
+
11
+ include Mayak::Cache
12
+
13
+ Key = type_member
14
+ Value = type_member
15
+
16
+ sig { void }
17
+ def initialize
18
+ @storage = T.let({}, T::Hash[Key, Value])
19
+ end
20
+
21
+ sig { override.params(key: Key).returns(T.nilable(Value)) }
22
+ def read(key)
23
+ @storage[key]
24
+ end
25
+
26
+ sig { override.params(key: Key, value: Value).void }
27
+ def write(key, value)
28
+ @storage[key] = value
29
+ end
30
+
31
+ sig { override.params(key: Key, blk: T.proc.returns(Value)).returns(Value) }
32
+ def fetch(key, &blk)
33
+ return T.must(@storage[key]) if @storage.has_key?(key)
34
+
35
+ value = blk.call
36
+ @storage[key] = value
37
+ value
38
+ end
39
+
40
+ sig { override.void }
41
+ def clear
42
+ @storage.clear
43
+ end
44
+
45
+ sig { override.params(key: Key).void }
46
+ def delete(key)
47
+ @storage.delete(key)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,62 @@
1
+ # Collections
2
+
3
+ Set of performant type safe collections written in ruby.
4
+
5
+ ### Queue
6
+
7
+ Regular parameterized FIFO queue with O(1) enqueue and dequeue operations. The difference between Mayak's Queue and standard library Queue is that former is plain data struture that doesn't block, while the latter is a synchronization multi-producer, multi-consumer queue.
8
+
9
+ Usage:
10
+ ```ruby
11
+ queue = Mayak::Collections::Queue[Integer].new
12
+
13
+ # adds an element to the queue
14
+ queue.enqueue(1)
15
+ queue.enqueue(2)
16
+ queue.enqueue(3)
17
+
18
+ # returns first queue element without updating the queue
19
+ queue.peak # 1
20
+
21
+ # returns an element and remove it from collection
22
+ queue.dequeue # 1
23
+ queue.dequeue # 2
24
+ queue.dequeue # 3
25
+ queue.dequeue # nil
26
+
27
+ # checks whether collection is empty
28
+ queue.empty? # true
29
+ queue.enqueue(1)
30
+ queue.empty? # false
31
+ ```
32
+
33
+ ### Priority Queue
34
+
35
+ Implements a queue where aach element has an associated priority. Elements with high priority are served before elements with low priority. The priority queue is parameterized with both element and priority types. Priority queue is initialized with a comparator function that returns a boolean value (true if a first argument is larger that a second).
36
+
37
+ Usage:
38
+ ```ruby
39
+ pqueue = Mayak::Collections::PriorityQueue[String, Integer].new { |a, b| a > b }
40
+
41
+ # adds an element with a priority
42
+ pqueue.enqueue("second", 8)
43
+ pqueue.enqueue("third", 5)
44
+ pqueue.enqueue("first", 10)
45
+ # returns a pair of value and priority and remove it from the queue
46
+ pqueue.dequeue # ["first", 10]
47
+ pqueue.dequeue # ["second", 8]
48
+ pqueue.dequeue # ["third", 5]
49
+
50
+ # returns a first pair without removing it from the queue
51
+ pqueue.peak # nil
52
+ pqueue.enqueue("first", 10)
53
+ pqueue.peak # ["first", 10]
54
+ pqueue.dequeue # ["first", 10]
55
+
56
+ pqueue.enqueue("second", 8)
57
+ pqueue.enqueue("third", 5)
58
+ pqueue.enqueue("first", 10)
59
+
60
+ # returns an array containing all pairs preserving internal heap structure
61
+ pqueue.to_a # [["first", 10], ["third", 5], ["second", 8]]
62
+ ```
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module Mayak
5
+ module Collections
6
+ class PriorityQueue
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ Element = type_member
11
+ Priority = type_member
12
+
13
+ sig { returns(Integer) }
14
+ attr_reader :size
15
+
16
+ sig { params(compare: T.proc.params(arg0: Priority, arg2: Priority).returns(T::Boolean)).void }
17
+ def initialize(&compare)
18
+ @array = T.let([], T::Array[T.nilable([Element, Priority])])
19
+ @compare = T.let(compare, T.proc.params(arg0: Priority, arg2: Priority).returns(T::Boolean))
20
+ @size = T.let(0, Integer)
21
+ end
22
+
23
+ sig { params(element: Element, priority: Priority).void }
24
+ def enqueue(element, priority)
25
+ @array[@size] = [element, priority]
26
+ @size += 1
27
+ sift_up(size - 1)
28
+ end
29
+
30
+ sig { returns(T.nilable([Element, Priority])) }
31
+ def dequeue
32
+ element_index = 0
33
+ result = @array[element_index]
34
+
35
+ if @size > 1
36
+ @size -= 1
37
+ @array[element_index] = @array[@size]
38
+ sift_down(element_index)
39
+ else
40
+ @size = 0
41
+ end
42
+
43
+ @array[@size] = nil
44
+
45
+ result
46
+ end
47
+
48
+ sig { returns(T.nilable([Element, Priority])) }
49
+ def peak
50
+ @array.first
51
+ end
52
+
53
+ sig { void }
54
+ def clear
55
+ @array = []
56
+ @size = 0
57
+ end
58
+
59
+ sig { returns(T::Array[[Element, Priority]]) }
60
+ def to_a
61
+ @array.compact
62
+ end
63
+
64
+ sig { returns(T::Boolean) }
65
+ def empty?
66
+ size == 0
67
+ end
68
+
69
+ private
70
+
71
+ sig { params(element_index: Integer).void }
72
+ def sift_up(element_index)
73
+ index = element_index
74
+
75
+ while !root?(index) && compare(index, parent_index(index))
76
+ swap(index, parent_index(index))
77
+ index = parent_index(index)
78
+ end
79
+ end
80
+
81
+ sig { params(element_index: Integer).void }
82
+ def sift_down(element_index)
83
+ index = element_index
84
+
85
+ loop do
86
+ left_index = left_child_index(index)
87
+ right_index = right_child_index(index)
88
+
89
+ if has_left_child(index) && compare(left_index, index)
90
+ swap(index, left_index)
91
+ index = left_index
92
+ elsif has_right_child(index) && compare(right_index, index)
93
+ swap(index, right_index)
94
+ index = right_index
95
+ else
96
+ break
97
+ end
98
+ end
99
+ end
100
+
101
+ sig { params(index1: Integer, index2: Integer).void }
102
+ def swap(index1, index2)
103
+ @array[index1], @array[index2] = @array[index2], @array[index1]
104
+ end
105
+
106
+ sig { params(index1: Integer, index2: Integer).returns(T::Boolean) }
107
+ def compare(index1, index2)
108
+ value1 = @array[index1]
109
+ value2 = @array[index2]
110
+
111
+ raise StandardError.new("index out of bound") if value1.nil? || value2.nil?
112
+
113
+ _, priority1 = value1
114
+ _, priority2 = value2
115
+
116
+ @compare.call(priority1, priority2)
117
+ end
118
+
119
+ sig { params(index: Integer).returns(T::Boolean) }
120
+ def has_parent(index)
121
+ index >= 1
122
+ end
123
+
124
+ sig { params(index: Integer).returns(Integer) }
125
+ def parent_index(index)
126
+ ((index - 1) / 2).floor
127
+ end
128
+
129
+ sig { params(index: Integer).returns(T::Boolean) }
130
+ def has_left_child(index)
131
+ left_child_index(index) < @size
132
+ end
133
+
134
+ sig { params(index: Integer).returns(Integer) }
135
+ def left_child_index(index)
136
+ index * 2 + 1
137
+ end
138
+
139
+ sig { params(index: Integer).returns(T::Boolean) }
140
+ def has_right_child(index)
141
+ right_child_index(index) < @size
142
+ end
143
+
144
+ sig { params(index: Integer).returns(Integer) }
145
+ def right_child_index(index)
146
+ index * 2 + 2
147
+ end
148
+
149
+ sig { params(index: Integer).returns(T::Boolean) }
150
+ def root?(index)
151
+ index == 0
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module Mayak
5
+ module Collections
6
+ class Queue
7
+ extend T::Sig
8
+ extend T::Helpers
9
+ extend T::Generic
10
+
11
+ Value = type_member
12
+
13
+ class Node < T::Struct
14
+ extend T::Sig
15
+ extend T::Generic
16
+
17
+ Value = type_member
18
+
19
+ const :value, Value
20
+ prop :next, T.nilable(Node[Value])
21
+ end
22
+ private_constant :Node
23
+
24
+ sig { returns(Integer) }
25
+ attr_reader :size
26
+
27
+ sig { params(initial: T::Array[Value]).void }
28
+ def initialize(initial: [])
29
+ @head = T.let(nil, T.nilable(Node[Value]))
30
+ @tail = T.let(nil, T.nilable(Node[Value]))
31
+ @size = T.let(0, Integer)
32
+ initial.each { |element| enqueue(element) }
33
+ end
34
+
35
+ sig { params(element: Value).void }
36
+ def enqueue(element)
37
+ if @head.nil?
38
+ @head = Node[Value].new(value: element, next: nil)
39
+ @tail = @head
40
+ @size += 1
41
+ else
42
+ T.must(@tail).next = Node[Value].new(value: element, next: nil)
43
+ @tail = T.must(@tail).next
44
+ @size += 1
45
+ end
46
+ end
47
+
48
+ sig { returns(T.nilable(Value)) }
49
+ def peak
50
+ return if @head.nil?
51
+
52
+ @head.value
53
+ end
54
+
55
+ sig { returns(T.nilable(Value)) }
56
+ def dequeue
57
+ return if @size == 0
58
+ return if @head.nil?
59
+
60
+ element = @head.value
61
+ @head = @head.next
62
+ @size -= 1
63
+ @tail = nil if @size == 0
64
+ element
65
+ end
66
+
67
+ sig { returns(T::Boolean) }
68
+ def empty?
69
+ @size == 0
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module Mayak
5
+ class Function
6
+ extend T::Sig
7
+ extend T::Helpers
8
+ extend T::Generic
9
+
10
+ Input = type_member
11
+ Output = type_member
12
+
13
+ sig { returns(T.proc.params(input: Input).returns(Output)) }
14
+ attr_reader :blk
15
+
16
+ sig { params(blk: T.proc.params(input: Input).returns(Output)).void }
17
+ def initialize(&blk)
18
+ @blk = T.let(blk, T.proc.params(input: Input).returns(Output))
19
+ end
20
+
21
+ sig { params(input: Input).returns(Output) }
22
+ def call(input)
23
+ blk.call(input)
24
+ end
25
+
26
+ sig {
27
+ type_parameters(:Output2)
28
+ .params(another: Mayak::Function[Output, T.type_parameter(:Output2)])
29
+ .returns(Mayak::Function[Input, T.type_parameter(:Output2)])
30
+ }
31
+ def and_then(another)
32
+ Mayak::Function[Input, T.type_parameter(:Output2)].new { |a| another.call(blk.call(a)) }
33
+ end
34
+ alias >> and_then
35
+
36
+ sig {
37
+ type_parameters(:Input2)
38
+ .params(another: Mayak::Function[T.type_parameter(:Input2), Input])
39
+ .returns(Mayak::Function[T.type_parameter(:Input2), Output])
40
+ }
41
+ def compose(another)
42
+ Mayak::Function[T.type_parameter(:Input2), Output].new { |a| blk.call(another.call(a)) }
43
+ end
44
+ alias << compose
45
+ end
46
+ end