t_algebra 0.1.2 → 0.1.6
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/Gemfile.lock +10 -1
- data/README.md +18 -15
- data/lib/t_algebra/monad/either.rb +13 -6
- data/lib/t_algebra/monad/maybe.rb +14 -1
- data/lib/t_algebra/monad/parser.rb +21 -11
- data/lib/t_algebra/monad/single_valued.rb +36 -0
- data/lib/t_algebra/monad.rb +30 -36
- data/lib/t_algebra/version.rb +1 -1
- data/lib/t_algebra.rb +6 -1
- data/t_algebra.gemspec +3 -0
- metadata +32 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c3ba123bba6480b11a43629310dcf4caa7f3c1995a8ca54a3694cec83aeb868
|
4
|
+
data.tar.gz: 3c47a5a18b15088b2d82d05c0662bda9c68fc72ca60085d7a3d3557a22c760eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6850ff73905c72cd22587c2c19d93cf97bc7eebd6543c33e5094e996e2e6e416499a85db1f482ccc37363a4aaa100a2d8f7409480d1b2b0376451a8945d2877d
|
7
|
+
data.tar.gz: 863dc96cd733cec7966a58d21ce8d9c1c8123850743975983fb630d4b71d94ef1a9267c3d436e0800eabbe751c10532a95413e3b2a10184c213b3ec763b084bb
|
data/Gemfile.lock
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
t_algebra (
|
4
|
+
t_algebra (1.1.1)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
+
benchmark-ips (2.9.2)
|
10
|
+
concurrent-ruby (1.1.9)
|
9
11
|
diff-lcs (1.4.4)
|
12
|
+
dry-core (0.7.1)
|
13
|
+
concurrent-ruby (~> 1.0)
|
14
|
+
dry-monads (1.4.0)
|
15
|
+
concurrent-ruby (~> 1.0)
|
16
|
+
dry-core (~> 0.7)
|
10
17
|
rake (12.3.3)
|
11
18
|
rspec (3.10.0)
|
12
19
|
rspec-core (~> 3.10.0)
|
@@ -26,6 +33,8 @@ PLATFORMS
|
|
26
33
|
ruby
|
27
34
|
|
28
35
|
DEPENDENCIES
|
36
|
+
benchmark-ips
|
37
|
+
dry-monads
|
29
38
|
rake (~> 12.0)
|
30
39
|
rspec (~> 3.0)
|
31
40
|
t_algebra!
|
data/README.md
CHANGED
@@ -43,16 +43,18 @@ end
|
|
43
43
|
```
|
44
44
|
|
45
45
|
The class `TAlgebra::Monad::Maybe` wraps a result which may or may not be nil, and uses the Monad tech of `bind` + `fmap` to
|
46
|
-
sensibly manipulate that result. The below example uses `
|
46
|
+
sensibly manipulate that result. The below example uses `chain/bound` notation (analagous to Haskell `do/<-` notation or js `async/await`)
|
47
47
|
to reproduce the same functionality:
|
48
48
|
|
49
49
|
```
|
50
50
|
# @return [TAlgebra::Monad::Maybe]
|
51
51
|
def get_address(user)
|
52
|
-
TAlgebra::Monad::Maybe.
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
TAlgebra::Monad::Maybe.chain do
|
53
|
+
m_user = just(user)
|
54
|
+
|
55
|
+
street = bound { m_user.fetch(:street) }
|
56
|
+
city = bound { m_user.fetch(:city) }
|
57
|
+
state = bound { m_user.fetch(:state) }
|
56
58
|
"#{street}, #{city} #{state.upcase}"
|
57
59
|
end
|
58
60
|
end
|
@@ -64,16 +66,16 @@ subsequently `Parser` which can validate and map over complex data structure.
|
|
64
66
|
```
|
65
67
|
# @return [TAlgebra::Monad::Parser]
|
66
68
|
def get_address(user)
|
67
|
-
TAlgebra::Monad::Parser.
|
69
|
+
TAlgebra::Monad::Parser.chain do
|
68
70
|
# validate street is present with `#fetch!`
|
69
|
-
street =
|
71
|
+
street = bound { fetch!(user, :street) }
|
70
72
|
|
71
73
|
# validate that the city is a string with is `#is_a?`
|
72
|
-
city =
|
74
|
+
city = bound { fetch!(user, :city).is_a?(String) }
|
73
75
|
|
74
76
|
# validate that the state is a 2 letter long string with `#validate`
|
75
77
|
# and transform to all caps with `#fmap`
|
76
|
-
state =
|
78
|
+
state = bound do
|
77
79
|
fetch!(user, :state)
|
78
80
|
.is_a?(String)
|
79
81
|
.validate("Is 2 letter code"){ |state| state.length == 2 }
|
@@ -96,7 +98,8 @@ class ExtAPI
|
|
96
98
|
|
97
99
|
class << self
|
98
100
|
def call(verb, path)
|
99
|
-
... make api cal
|
101
|
+
result = ... make api cal
|
102
|
+
new(success: result)
|
100
103
|
rescue => e
|
101
104
|
new(http_error: {e.status, e.message})
|
102
105
|
end
|
@@ -121,16 +124,16 @@ And it could be used like:
|
|
121
124
|
|
122
125
|
```
|
123
126
|
def user_cities
|
124
|
-
ExtAPI.
|
125
|
-
users =
|
126
|
-
profiles =
|
127
|
+
ExtAPI.chain do
|
128
|
+
users = bound { call('GET', '/users') }
|
129
|
+
profiles = bound { call('GET', "/users/profile?id=#{users.map(&:id).to_json}" }
|
127
130
|
profiles.map(&:city).uniq
|
128
131
|
end
|
129
132
|
end
|
130
133
|
```
|
131
134
|
|
132
|
-
|
133
|
-
|
135
|
+
Should either of the two calls fail, this method will return that error status and message to be handled by the client code.
|
136
|
+
(Note that while the concept of Promise is monadic, the above ExtAPI implementation is entirely synchronous.)
|
134
137
|
|
135
138
|
## Development
|
136
139
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module TAlgebra
|
2
2
|
module Monad
|
3
3
|
class Either
|
4
|
-
include TAlgebra::Monad
|
4
|
+
include TAlgebra::Monad::SingleValued
|
5
5
|
|
6
6
|
LEFT = :left
|
7
7
|
RIGHT = :right
|
@@ -17,16 +17,18 @@ module TAlgebra
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
def fmap
|
21
|
-
|
20
|
+
def fmap
|
21
|
+
raise UseError.new("#fmap requires a block") unless block_given?
|
22
|
+
return dup if left?
|
22
23
|
|
23
24
|
self.class.pure(yield(value))
|
24
25
|
end
|
25
26
|
|
26
|
-
def bind
|
27
|
-
|
27
|
+
def bind
|
28
|
+
raise UseError.new("#bind requires a block") unless block_given?
|
29
|
+
return dup if left?
|
28
30
|
|
29
|
-
|
31
|
+
yield value
|
30
32
|
end
|
31
33
|
|
32
34
|
def left?
|
@@ -37,7 +39,12 @@ module TAlgebra
|
|
37
39
|
is == RIGHT
|
38
40
|
end
|
39
41
|
|
42
|
+
def from_either!
|
43
|
+
from_either { |e| raise UnsafeError.new("#from_either! exception. #{e}") }
|
44
|
+
end
|
45
|
+
|
40
46
|
def from_either
|
47
|
+
raise UseError.new("#from_either called without block") unless block_given?
|
41
48
|
return yield(value) if left?
|
42
49
|
value
|
43
50
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module TAlgebra
|
2
2
|
module Monad
|
3
3
|
class Maybe
|
4
|
-
include TAlgebra::Monad
|
4
|
+
include TAlgebra::Monad::SingleValued
|
5
5
|
|
6
6
|
NOTHING = :nothing
|
7
7
|
JUST = :just
|
@@ -39,11 +39,24 @@ module TAlgebra
|
|
39
39
|
is == JUST
|
40
40
|
end
|
41
41
|
|
42
|
+
def from_maybe!
|
43
|
+
from_maybe { |e| raise UnsafeError.new("#from_maybe! exception. #{e}") }
|
44
|
+
end
|
45
|
+
|
42
46
|
def from_maybe
|
47
|
+
raise UseError.new("#from_maybe called without block") unless block_given?
|
43
48
|
return yield if nothing?
|
44
49
|
value
|
45
50
|
end
|
46
51
|
|
52
|
+
def fetch(key)
|
53
|
+
bind do |o|
|
54
|
+
self.class.to_maybe(
|
55
|
+
o.respond_to?(:[]) ? o[key] : o.send(key)
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
47
60
|
def ==(other)
|
48
61
|
to_obj == other.to_obj
|
49
62
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module TAlgebra
|
2
2
|
module Monad
|
3
3
|
class Parser < Either
|
4
|
-
include TAlgebra::Monad
|
4
|
+
include TAlgebra::Monad::SingleValued
|
5
5
|
|
6
6
|
def initialize(is:, value:, name: nil)
|
7
7
|
super(is: is, value: value)
|
@@ -10,38 +10,44 @@ module TAlgebra
|
|
10
10
|
|
11
11
|
class << self
|
12
12
|
def failure(msg, name = nil)
|
13
|
-
|
13
|
+
new(is: Either::LEFT, value: msg, name: name)
|
14
14
|
end
|
15
15
|
|
16
16
|
def parse(val, name = nil)
|
17
|
-
|
17
|
+
new(is: Either::RIGHT, value: val, name: name)
|
18
18
|
end
|
19
19
|
|
20
20
|
def fetch(o, key)
|
21
|
-
|
22
|
-
parser.with_name(key).optional
|
21
|
+
parse(o).fetch(key)
|
23
22
|
end
|
24
23
|
|
25
24
|
def fetch!(o, key)
|
26
|
-
|
27
|
-
parser.with_name(key).required
|
25
|
+
parse(o).fetch!(key)
|
28
26
|
end
|
29
27
|
|
30
|
-
def
|
28
|
+
def chain_bind(ma, &block)
|
31
29
|
raise "Yield blocks must return instances of #{self}. Got #{ma.class}" unless [Parser, Parser::Optional].include?(ma.class)
|
32
30
|
|
33
31
|
ma.as_parser.bind(&block)
|
34
32
|
end
|
35
33
|
end
|
36
34
|
|
35
|
+
alias_method :valid?, :right?
|
36
|
+
alias_method :failure?, :left?
|
37
|
+
|
37
38
|
attr_reader :name
|
38
39
|
def with_name(name)
|
39
40
|
@name = name
|
40
41
|
self
|
41
42
|
end
|
42
43
|
|
43
|
-
|
44
|
-
|
44
|
+
def fetch(key)
|
45
|
+
with_name(key).fmap { |o| o.respond_to?(:[]) ? o[key] : o.send(key) }.optional
|
46
|
+
end
|
47
|
+
|
48
|
+
def fetch!(key)
|
49
|
+
with_name(key).fmap { |o| o.respond_to?(:[]) ? o[key] : o.send(key) }.required
|
50
|
+
end
|
45
51
|
|
46
52
|
def fmap
|
47
53
|
super.with_name(name)
|
@@ -63,7 +69,7 @@ module TAlgebra
|
|
63
69
|
|
64
70
|
def validate(msg = "Invalid")
|
65
71
|
n = name
|
66
|
-
bind { |val| yield(val) ? parse(val, n) : failure(msg, n) }
|
72
|
+
bind { |val| yield(val) ? Parser.parse(val, n) : Parser.failure(msg, n) }
|
67
73
|
end
|
68
74
|
|
69
75
|
def extract_parsed(&block)
|
@@ -71,6 +77,10 @@ module TAlgebra
|
|
71
77
|
from_either(&block)
|
72
78
|
end
|
73
79
|
|
80
|
+
def extract_parsed!
|
81
|
+
extract_parsed { |e| raise UnsafeError.new("#extract_parsed! exception. #{e}") }
|
82
|
+
end
|
83
|
+
|
74
84
|
def required
|
75
85
|
validate("Is required") { |v| !v.nil? }
|
76
86
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "fiber"
|
2
|
+
# @abstract
|
3
|
+
module TAlgebra
|
4
|
+
module Monad
|
5
|
+
module SingleValued
|
6
|
+
module Static
|
7
|
+
def chain(&block)
|
8
|
+
receiver = augmented_receiver(block)
|
9
|
+
fiber = Fiber.new { receiver.instance_exec(&block) }
|
10
|
+
chain_recursive(fiber, [])
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def chain_recursive(fiber, current)
|
16
|
+
val = fiber.resume current
|
17
|
+
|
18
|
+
if fiber.alive?
|
19
|
+
chain_bind(val.call) { |subsequent| chain_recursive(fiber, subsequent) }
|
20
|
+
else
|
21
|
+
val.is_a?(self.class) ? val : pure(val)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def included(base)
|
28
|
+
base.class_eval do
|
29
|
+
include TAlgebra::Monad
|
30
|
+
end
|
31
|
+
base.extend(Static)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/t_algebra/monad.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "fiber"
|
1
2
|
# @abstract
|
2
3
|
module TAlgebra
|
3
4
|
module Monad
|
@@ -6,65 +7,58 @@ module TAlgebra
|
|
6
7
|
end
|
7
8
|
|
8
9
|
module Static
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def chain(&block)
|
11
|
+
receiver = augmented_receiver(block)
|
12
|
+
fiber_initializer = -> { Fiber.new { receiver.instance_exec(&block) } }
|
13
|
+
chain_recursive(fiber_initializer, [])
|
14
|
+
end
|
15
|
+
alias_method :run, :chain
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
end
|
17
|
+
def bound(&block)
|
18
|
+
Fiber.yield(block)
|
17
19
|
end
|
20
|
+
alias_method :_pick, :bound
|
21
|
+
|
22
|
+
def augmented_receiver(block)
|
23
|
+
block_self = block.binding.receiver
|
24
|
+
|
25
|
+
self_class = self
|
26
|
+
block_self.define_singleton_method(:method_missing) do |m, *args, &block|
|
27
|
+
self_class.send(m, *args, &block)
|
28
|
+
end
|
18
29
|
|
19
|
-
|
20
|
-
e = Enumerator.new { |y| instance_exec(LazyYielder.new(y), &block) }
|
21
|
-
run_recursive(e, [])
|
30
|
+
block_self
|
22
31
|
end
|
23
32
|
|
24
|
-
def
|
33
|
+
def chain_bind(ma, &block)
|
25
34
|
raise "Yield blocks must return instances of #{self}" unless ma.instance_of?(self)
|
26
|
-
ma.bind
|
35
|
+
ma.bind(&block)
|
27
36
|
end
|
28
37
|
|
29
|
-
def lift_a2(ma, mb
|
38
|
+
def lift_a2(ma, mb)
|
30
39
|
ma.bind do |a|
|
31
40
|
mb.bind do |b|
|
32
|
-
pure(
|
41
|
+
pure(yield(a, b))
|
33
42
|
end
|
34
43
|
end
|
35
44
|
end
|
36
45
|
|
37
46
|
private
|
38
47
|
|
39
|
-
def
|
40
|
-
|
48
|
+
def chain_recursive(fiber_initializer, historical_values)
|
49
|
+
fiber = fiber_initializer.call
|
41
50
|
|
51
|
+
val = fiber.resume
|
42
52
|
historical_values.each do |h|
|
43
|
-
|
44
|
-
enum.feed(h)
|
53
|
+
val = fiber.resume h
|
45
54
|
end
|
46
55
|
|
47
|
-
if
|
48
|
-
|
56
|
+
if fiber.alive?
|
57
|
+
chain_bind(val.call) { |a| chain_recursive(fiber_initializer, historical_values + [a]) }
|
49
58
|
else
|
50
|
-
|
51
|
-
run_recursive(enum, historical_values + [a])
|
52
|
-
end
|
59
|
+
val.is_a?(self.class) ? val : pure(val)
|
53
60
|
end
|
54
61
|
end
|
55
|
-
|
56
|
-
def is_complete(enumerator)
|
57
|
-
enumerator.peek
|
58
|
-
false
|
59
|
-
rescue StopIteration
|
60
|
-
true
|
61
|
-
end
|
62
|
-
|
63
|
-
def value(enumerator)
|
64
|
-
enumerator.peek
|
65
|
-
rescue StopIteration
|
66
|
-
$!.result
|
67
|
-
end
|
68
62
|
end
|
69
63
|
|
70
64
|
class << self
|
data/lib/t_algebra/version.rb
CHANGED
data/lib/t_algebra.rb
CHANGED
@@ -2,6 +2,7 @@ require_relative "t_algebra/version"
|
|
2
2
|
require_relative "t_algebra/functor"
|
3
3
|
require_relative "t_algebra/applicative"
|
4
4
|
require_relative "t_algebra/monad"
|
5
|
+
require_relative "t_algebra/monad/single_valued"
|
5
6
|
require_relative "t_algebra/monad/maybe"
|
6
7
|
require_relative "t_algebra/monad/either"
|
7
8
|
require_relative "t_algebra/monad/list"
|
@@ -9,5 +10,9 @@ require_relative "t_algebra/monad/reader"
|
|
9
10
|
require_relative "t_algebra/monad/parser"
|
10
11
|
|
11
12
|
module TAlgebra
|
12
|
-
class
|
13
|
+
class UseError < StandardError
|
14
|
+
end
|
15
|
+
|
16
|
+
class UnsafeError < StandardError
|
17
|
+
end
|
13
18
|
end
|
data/t_algebra.gemspec
CHANGED
@@ -17,6 +17,9 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.metadata["source_code_uri"] = "https://github.com/afg419/t_algebra"
|
18
18
|
spec.metadata["changelog_uri"] = "https://example.com"
|
19
19
|
|
20
|
+
spec.add_development_dependency("dry-monads")
|
21
|
+
spec.add_development_dependency("benchmark-ips")
|
22
|
+
|
20
23
|
# Specify which files should be added to the gem when it is released.
|
21
24
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
25
|
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: t_algebra
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- aaron
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-12-
|
12
|
-
dependencies:
|
11
|
+
date: 2021-12-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-monads
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: benchmark-ips
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
13
41
|
description:
|
14
42
|
email:
|
15
43
|
- afg419@gmail.com
|
@@ -37,6 +65,7 @@ files:
|
|
37
65
|
- lib/t_algebra/monad/maybe.rb
|
38
66
|
- lib/t_algebra/monad/parser.rb
|
39
67
|
- lib/t_algebra/monad/reader.rb
|
68
|
+
- lib/t_algebra/monad/single_valued.rb
|
40
69
|
- lib/t_algebra/version.rb
|
41
70
|
- t_algebra.gemspec
|
42
71
|
homepage: https://github.com/afg419/t_algebra
|