t_algebra 0.1.2 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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