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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c61dbc20ba8e3f3b37778601949560237c224902454ab26b935c5dc048c8511
4
- data.tar.gz: 1a9f3e3b7dc17c500c7e827514cc332b1ac9d4c5da919d8de9c4cd16cb1fc6a8
3
+ metadata.gz: 2c3ba123bba6480b11a43629310dcf4caa7f3c1995a8ca54a3694cec83aeb868
4
+ data.tar.gz: 3c47a5a18b15088b2d82d05c0662bda9c68fc72ca60085d7a3d3557a22c760eb
5
5
  SHA512:
6
- metadata.gz: 823c7c82e469fb68b44cb00dda7f796ce1d8c1717c9cb312e659d42a68d21a46b43c34b8af08792728fb13042302138b212c9ec93d1ccf1b338ba13036c6afb8
7
- data.tar.gz: 0b6223275fa9dc3b21e31ad1a0975457e3214351aa4b008cd1e894ef51bd2fca60e60159a42f9c55f9d5ce49da8fd487502b7d4ce3991891ba6c452e6ecf5899
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 (0.1.1)
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 `run/yield` notation (analagous to Haskell `do` notation or js `async/await`)
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.run do |y|
53
- street = y.yield { to_maybe(user.street) }
54
- city = y.yield { to_maybe(user.city) }
55
- state = y.yield { to_maybe(user.state) }
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.run do |y|
69
+ TAlgebra::Monad::Parser.chain do
68
70
  # validate street is present with `#fetch!`
69
- street = y.yield { fetch!(user, :street) }
71
+ street = bound { fetch!(user, :street) }
70
72
 
71
73
  # validate that the city is a string with is `#is_a?`
72
- city = y.yield { fetch!(user, :city).is_a?(String) }
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 = y.yield do
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.run do |y|
125
- users = y.yield { call('GET', '/users') }
126
- profiles = y.yield { call('GET', "/users/profile?id=#{users.map(&:id).to_json}" }
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
- (Note that while the concept of Promise is monadic, the above ExtAPI implementation is entirely synchronous.) Should
133
- either of the two calls fail, this method will return that error status and message to be handled by the client code.
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(&block)
21
- return dup if left? || !block
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(&block)
27
- return dup if left? || !block
27
+ def bind
28
+ raise UseError.new("#bind requires a block") unless block_given?
29
+ return dup if left?
28
30
 
29
- self.class.instance_exec(value, &block)
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
- left(msg).with_name(name)
13
+ new(is: Either::LEFT, value: msg, name: name)
14
14
  end
15
15
 
16
16
  def parse(val, name = nil)
17
- right(val).with_name(name)
17
+ new(is: Either::RIGHT, value: val, name: name)
18
18
  end
19
19
 
20
20
  def fetch(o, key)
21
- parser = parse(o.respond_to?(:[]) ? o[key] : o.send(key))
22
- parser.with_name(key).optional
21
+ parse(o).fetch(key)
23
22
  end
24
23
 
25
24
  def fetch!(o, key)
26
- parser = parse(o.respond_to?(:[]) ? o[key] : o.send(key))
27
- parser.with_name(key).required
25
+ parse(o).fetch!(key)
28
26
  end
29
27
 
30
- def run_bind(ma, &block)
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
- alias_method :valid?, :right?
44
- alias_method :failure?, :left?
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
@@ -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
- class LazyYielder
10
- def initialize(yielder)
11
- @yielder = yielder
12
- end
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
- def yield(&block)
15
- @yielder.yield(block)
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
- def run(&block)
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 run_bind(ma)
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, &block)
38
+ def lift_a2(ma, mb)
30
39
  ma.bind do |a|
31
40
  mb.bind do |b|
32
- pure(block.call(a, b))
41
+ pure(yield(a, b))
33
42
  end
34
43
  end
35
44
  end
36
45
 
37
46
  private
38
47
 
39
- def run_recursive(enum, historical_values)
40
- enum.rewind
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
- enum.next
44
- enum.feed(h)
53
+ val = fiber.resume h
45
54
  end
46
55
 
47
- if is_complete(enum)
48
- pure(value(enum))
56
+ if fiber.alive?
57
+ chain_bind(val.call) { |a| chain_recursive(fiber_initializer, historical_values + [a]) }
49
58
  else
50
- run_bind(enum.next.call) do |a|
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
@@ -1,3 +1,3 @@
1
1
  module TAlgebra
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.6"
3
3
  end
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 Error < StandardError; end
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.2
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-06 00:00:00.000000000 Z
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