t_algebra 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 89594acf3cdd9218fe50f860b61da46dd2c5455acc7fa8164eaf7a2ff1a71235
4
+ data.tar.gz: d16301f3a688e0bceb6c83221fd8a8c33f9116a2e1cf9c68e9b2b6aa3b71347e
5
+ SHA512:
6
+ metadata.gz: 61d5e80838234214bdb56c0a72b91e03a3fcf1679f8a7f8914f9731868f0dccab1c47cf2a8ba857d861b1634903cc5733874786604e5dc04708db351224e6c60
7
+ data.tar.gz: 422676bbe62f022fba90309180cfbb243f5197d9b1527aaa691e1b1646cd74178f2892bd1047fa347b53a83ca19b30d983e3377d4b07574ec098c4b9bf76e5c4
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .idea
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.4
6
+ before_install: gem install bundler -v 2.1.4
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at afg419@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in t_algebra.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
data/Gemfile.lock ADDED
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ t_algebra (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.4.4)
10
+ rake (12.3.3)
11
+ rspec (3.10.0)
12
+ rspec-core (~> 3.10.0)
13
+ rspec-expectations (~> 3.10.0)
14
+ rspec-mocks (~> 3.10.0)
15
+ rspec-core (3.10.1)
16
+ rspec-support (~> 3.10.0)
17
+ rspec-expectations (3.10.1)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.10.0)
20
+ rspec-mocks (3.10.2)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.10.0)
23
+ rspec-support (3.10.3)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ rake (~> 12.0)
30
+ rspec (~> 3.0)
31
+ t_algebra!
32
+
33
+ BUNDLED WITH
34
+ 2.1.4
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 aaron
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # TAlgebra
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ ```ruby
8
+ gem 't_algebra'
9
+ ```
10
+
11
+ And then execute:
12
+
13
+ $ bundle install
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install t_algebra
18
+
19
+ ## Usage
20
+
21
+ t_algebra brings the basic categorical concepts of Functor, Applicative, and Monad into ruby, along with a handful of common instances.
22
+
23
+ Let's walk through `TAlgebra::Monad::Maybe`, the monad representing optional results. Typically in ruby we have a method
24
+ which may return a value or nil should some underlying data be absent. For example `user.city` may return `nil` should the
25
+ user not have entered their city. Working with these possible `nil` values can be a source of bugs and a source of code
26
+ complexity. The following code is illustrative:
27
+
28
+ ```
29
+ # @return [String, nil]
30
+ def get_address(user)
31
+ street = user.street
32
+ return unless street
33
+
34
+ city = user.city
35
+ return unless city
36
+
37
+ state = user.state
38
+ return unless state
39
+ state = state.upcase
40
+
41
+ "#{street}, #{city} #{state}" if street && city && state
42
+ end
43
+ ```
44
+
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`)
47
+ to reproduce the same functionality:
48
+
49
+ ```
50
+ # @return [TAlgebra::Monad::Maybe]
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) }
56
+ "#{street}, #{city} #{state.upcase}"
57
+ end
58
+ end
59
+ ```
60
+
61
+ This basic Maybe functionality extends neatly into the `Either` monad, the monad representing results or errors, and
62
+ subsequently `Parser` which can validate and map over complex data structure.
63
+
64
+ ```
65
+ # @return [TAlgebra::Monad::Parser}]
66
+ def get_address(user)
67
+ TAlgebra::Monad::Parser.run do |y|
68
+ # validate street is present with `#fetch!`
69
+ street = y.yield { fetch!(user, :street) }
70
+
71
+ # validate that the city is a string with is `#is_a?`
72
+ city = y.yield { fetch!(user, :city).is_a?(String) }
73
+
74
+ # validate that the state is a 2 letter long string with `#validate`
75
+ # and transform to all caps with `#fmap`
76
+ state = y.yield do
77
+ fetch!(user, :state)
78
+ .is_a?(String)
79
+ .validate("Is 2 letter code"){ |state| state.length == 2 }
80
+ .fmap(&:upcase)
81
+ end
82
+ "#{street}, #{city} #{state}"
83
+ end
84
+ end
85
+ ```
86
+
87
+ ### Defining your own examples
88
+
89
+ Say we had a class `ExtAPI` representing a an external api result or http error. (Similar to the Either monad.) We could implement this as a monad as
90
+ follows:
91
+
92
+ ```
93
+ class ExtAPI
94
+ include TAlgebra::Monad
95
+
96
+ class << self
97
+ def call(verb, path)
98
+ ... make api cal
99
+ rescue => e
100
+ new(http_error: {e.status, e.message})
101
+ end
102
+
103
+ #Implement Applicative's `.pure` interface
104
+ def pure(a)
105
+ new(success: a)
106
+ end
107
+ end
108
+
109
+ #Implement Monad's `#bind` interface
110
+ def bind
111
+ return new(http_error: http_error) if http_error
112
+ yield(success)
113
+ end
114
+
115
+ ...
116
+ end
117
+ ```
118
+
119
+ And it could be used like:
120
+
121
+ ```
122
+ def user_cities
123
+ ExtAPI.run do |y|
124
+ users = y.yield { call('GET', '/users') }
125
+ profiles = y.yield { call('GET', "/users/profile?id=#{users.map(&:id).to_json}" }
126
+ profiles.map(&:city).uniq
127
+ end
128
+ end
129
+ ```
130
+
131
+ (Note that while the concept of Promise is monadic, the above ExtAPI implementation is entirely synchronous.) Should
132
+ either of the two calls fail, this method will return that error status and message to be handled by the client code.
133
+
134
+ ## Development
135
+
136
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
137
+
138
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
139
+
140
+ ## Contributing
141
+
142
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/t_algebra. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/t_algebra/blob/master/CODE_OF_CONDUCT.md).
143
+
144
+
145
+ ## License
146
+
147
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
148
+
149
+ ## Code of Conduct
150
+
151
+ Everyone interacting in the TAlgebra project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/afg419/t_algebra/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "t_algebra/algebra"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't_algebra forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,53 @@
1
+ # @abstract
2
+ module TAlgebra
3
+ module Applicative
4
+ module Static
5
+ def pure(_)
6
+ raise "Implement .pure in extending class"
7
+ end
8
+
9
+ def lift_a2(_fa, _fb, &_block)
10
+ raise "Implement .lift_a2 in extending class"
11
+ end
12
+
13
+ def seq_a(*array_of_parsers, **hash_of_parsers)
14
+ return sequence_array(array_of_parsers) unless array_of_parsers.empty?
15
+
16
+ sequence_hash(hash_of_parsers) unless hash_of_parsers.empty?
17
+ end
18
+
19
+ # @param [Array<Applicative>] fbs
20
+ # @return [Applicative<Array>]
21
+ def sequence_array(fbs)
22
+ return pure([]) if fbs.empty?
23
+
24
+ fbs.reduce(pure([])) do |prev, fb|
25
+ lift_a2(prev, fb) do |p, b|
26
+ p + [b]
27
+ end
28
+ end
29
+ end
30
+
31
+ # @param [Hash<Any, Applicative>] fbs
32
+ # @return [Applicative<Hash>]
33
+ def sequence_hash(fbs)
34
+ return pure({}) if fbs.empty?
35
+
36
+ fbs.reduce(pure({})) do |prev, (k, fb)|
37
+ lift_a2(prev, fb) do |p, b|
38
+ p.merge(k => b)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ class << self
45
+ def included(base)
46
+ base.class_eval do
47
+ include TAlgebra::Functor
48
+ end
49
+ base.extend(Static)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,7 @@
1
+ module TAlgebra
2
+ module Functor
3
+ def fmap(&block)
4
+ raise "implement #fmap in extending class"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,67 @@
1
+ module TAlgebra
2
+ module Monad
3
+ class Either
4
+ include TAlgebra::Monad
5
+
6
+ LEFT = :left
7
+ RIGHT = :right
8
+
9
+ class << self
10
+ def pure(value)
11
+ new(is: RIGHT, value: value)
12
+ end
13
+ alias_method :right, :pure
14
+
15
+ def left(err)
16
+ new(is: LEFT, value: err)
17
+ end
18
+ end
19
+
20
+ def fmap(&block)
21
+ return dup if left? || !block
22
+
23
+ self.class.pure(yield(value))
24
+ end
25
+
26
+ def bind(&block)
27
+ return dup if left? || !block
28
+
29
+ self.class.instance_exec(value, &block)
30
+ end
31
+
32
+ def left?
33
+ is == LEFT
34
+ end
35
+
36
+ def right?
37
+ is == RIGHT
38
+ end
39
+
40
+ def from_either
41
+ return yield(value) if left?
42
+ value
43
+ end
44
+
45
+ def ==(other)
46
+ to_obj == other.to_obj
47
+ end
48
+
49
+ def to_obj
50
+ {is.to_s => value, :class => self.class.name}
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :is, :value
56
+
57
+ def initialize(is:, value:)
58
+ @is = is
59
+ @value = value
60
+ end
61
+
62
+ def dup
63
+ self.class.new(is: is, value: value)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,40 @@
1
+ module TAlgebra
2
+ module Monad
3
+ class List
4
+ include TAlgebra::Monad
5
+
6
+ class << self
7
+ alias_method :pure, :new
8
+ end
9
+
10
+ def fmap(&block)
11
+ self.class.pure(
12
+ *values.map { |l| yield(l) }
13
+ )
14
+ end
15
+
16
+ def bind(&block)
17
+ self.class.pure(
18
+ *values.reduce([]) do |acc, l|
19
+ acc.concat(block.call(l).send(:values))
20
+ end
21
+ )
22
+ end
23
+
24
+ def from_list
25
+ values
26
+ end
27
+
28
+ def ==(other)
29
+ from_list == other.from_list
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :values
35
+ def initialize(*values)
36
+ @values = values || []
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,68 @@
1
+ module TAlgebra
2
+ module Monad
3
+ class Maybe
4
+ include TAlgebra::Monad
5
+
6
+ NOTHING = :nothing
7
+ JUST = :just
8
+
9
+ class << self
10
+ def pure(value)
11
+ new(is: JUST, value: value)
12
+ end
13
+ alias_method :just, :pure
14
+
15
+ def nothing
16
+ new(is: NOTHING)
17
+ end
18
+
19
+ def to_maybe(value_or_nil)
20
+ value_or_nil.nil? ? nothing : pure(value_or_nil)
21
+ end
22
+ end
23
+
24
+ def fmap(&block)
25
+ return dup if nothing? || !block
26
+ self.class.just(yield(value))
27
+ end
28
+
29
+ def bind(&block)
30
+ return dup if nothing? || !block
31
+ yield value
32
+ end
33
+
34
+ def nothing?
35
+ is == NOTHING
36
+ end
37
+
38
+ def just?
39
+ is == JUST
40
+ end
41
+
42
+ def from_maybe
43
+ return yield if nothing?
44
+ value
45
+ end
46
+
47
+ def ==(other)
48
+ to_obj == other.to_obj
49
+ end
50
+
51
+ def to_obj
52
+ {is.to_s => value, :class => self.class.name}
53
+ end
54
+
55
+ private
56
+
57
+ attr_reader :is, :value
58
+ def initialize(is:, value: nil)
59
+ @is = is
60
+ @value = value
61
+ end
62
+
63
+ def dup
64
+ self.class.new(is: is, value: value)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,101 @@
1
+ module TAlgebra
2
+ module Monad
3
+ class Parser < Either
4
+ include TAlgebra::Monad
5
+
6
+ def initialize(is:, value:, name: nil)
7
+ super(is: is, value: value)
8
+ @name = name
9
+ end
10
+
11
+ class << self
12
+ def failure(msg, name = nil)
13
+ left(msg).with_name(name)
14
+ end
15
+
16
+ def parse(val, name = nil)
17
+ right(val).with_name(name)
18
+ end
19
+
20
+ def fetch(o, key)
21
+ parser = parse(o.respond_to?(:[]) ? o[key] : o.send(key))
22
+ parser.with_name(key).optional
23
+ end
24
+
25
+ def fetch!(o, key)
26
+ parser = parse(o.respond_to?(:[]) ? o[key] : o.send(key))
27
+ parser.with_name(key).required
28
+ end
29
+ end
30
+
31
+ attr_reader :name
32
+ def with_name(name)
33
+ @name = name
34
+ self
35
+ end
36
+
37
+ alias_method :valid?, :right?
38
+ alias_method :failure?, :left?
39
+
40
+ def fmap
41
+ super.with_name(name)
42
+ rescue => e
43
+ self.class.failure("Unable to fmap: #{e}", name)
44
+ end
45
+
46
+ def bind
47
+ super
48
+ rescue => e
49
+ self.class.failure("Unable to bind: #{e}", name)
50
+ end
51
+
52
+ def is_a?(*klasses)
53
+ validate("Must be type #{klasses.join(", ")}") do |v|
54
+ klasses.any? { |k| v.is_a?(k) }
55
+ end
56
+ end
57
+
58
+ def validate(msg = "Invalid")
59
+ n = name
60
+ bind { |val| yield(val) ? parse(val, n) : failure(msg, n) }
61
+ end
62
+
63
+ def extract_parsed(&block)
64
+ return yield("#{name}: #{value}") if left? && !name.nil?
65
+ from_either(&block)
66
+ end
67
+
68
+ def required
69
+ validate("Is required") { |v| !v.nil? }
70
+ end
71
+
72
+ def optional
73
+ Optional.new(is: is, value: value, name: name)
74
+ end
75
+
76
+ class Optional < Parser
77
+ def bind(&block)
78
+ return dup if failure? || value.nil?
79
+
80
+ required.bind(&block).optional
81
+ end
82
+
83
+ def fmap(&block)
84
+ return dup if failure? || value.nil?
85
+
86
+ required.fmap(&block).optional
87
+ end
88
+
89
+ def required
90
+ Parser.new(is: is, value: value, name: name).required
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def dup
97
+ self.class.new(is: is, value: value, name: name)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,42 @@
1
+ module TAlgebra
2
+ module Monad
3
+ class Reader
4
+ include TAlgebra::Monad
5
+
6
+ class << self
7
+ def pure(a)
8
+ new { a }
9
+ end
10
+
11
+ def ask
12
+ new { |r| r }
13
+ end
14
+ end
15
+
16
+ def fmap(&a_to_b)
17
+ Reader.new(&(r_to_a >> a_to_b))
18
+ end
19
+
20
+ def bind(&a_to_mb)
21
+ Reader.new do |r|
22
+ (r_to_a >> a_to_mb).call(r).run_reader(r)
23
+ end
24
+ end
25
+
26
+ def run_reader(r)
27
+ r_to_a.call(r)
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :r_to_a
33
+ def initialize(&r_to_a)
34
+ @r_to_a = r_to_a
35
+ end
36
+
37
+ def dup
38
+ self.class.new(&r_to_a)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,74 @@
1
+ # @abstract
2
+ module TAlgebra
3
+ module Monad
4
+ def bind(&block)
5
+ raise "Implement #bind in extending class"
6
+ end
7
+
8
+ module Static
9
+ class LazyYielder
10
+ def initialize(yielder)
11
+ @yielder = yielder
12
+ end
13
+
14
+ def yield(&block)
15
+ @yielder.yield(block)
16
+ end
17
+ end
18
+
19
+ def run(&block)
20
+ e = Enumerator.new { |y| instance_exec(LazyYielder.new(y), &block) }
21
+ run_recursive(e, [])
22
+ end
23
+
24
+ def lift_a2(ma, mb, &block)
25
+ ma.bind do |a|
26
+ mb.bind do |b|
27
+ pure(block.call(a, b))
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def run_recursive(enum, historical_values)
35
+ enum.rewind
36
+
37
+ historical_values.each do |h|
38
+ enum.next
39
+ enum.feed(h)
40
+ end
41
+
42
+ if is_complete(enum)
43
+ pure(value(enum))
44
+ else
45
+ enum.next.call.bind do |a|
46
+ run_recursive(enum, historical_values + [a])
47
+ end
48
+ end
49
+ end
50
+
51
+ def is_complete(enumerator)
52
+ enumerator.peek
53
+ false
54
+ rescue StopIteration
55
+ true
56
+ end
57
+
58
+ def value(enumerator)
59
+ enumerator.peek
60
+ rescue StopIteration
61
+ $!.result
62
+ end
63
+ end
64
+
65
+ class << self
66
+ def included(base)
67
+ base.class_eval do
68
+ include TAlgebra::Applicative
69
+ end
70
+ base.extend(Static)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ module TAlgebra
2
+ VERSION = "0.1.0"
3
+ end
data/lib/t_algebra.rb ADDED
@@ -0,0 +1,13 @@
1
+ require_relative "t_algebra/version"
2
+ require_relative "t_algebra/functor"
3
+ require_relative "t_algebra/applicative"
4
+ require_relative "t_algebra/monad"
5
+ require_relative "t_algebra/monad/maybe"
6
+ require_relative "t_algebra/monad/either"
7
+ require_relative "t_algebra/monad/list"
8
+ require_relative "t_algebra/monad/reader"
9
+ require_relative "t_algebra/monad/parser"
10
+
11
+ module TAlgebra
12
+ class Error < StandardError; end
13
+ end
data/t_algebra.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ require_relative "lib/t_algebra/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "t_algebra"
5
+ spec.version = TAlgebra::VERSION
6
+ spec.authors = ["aaron"]
7
+ spec.email = ["afg419@gmail.com"]
8
+
9
+ spec.summary = "Functor, Applicative, and Monad interfaces for Ruby"
10
+ spec.homepage = "https://github.com/afg419/t_algebra"
11
+ spec.license = "MIT"
12
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
13
+
14
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/afg419/t_algebra"
18
+ spec.metadata["changelog_uri"] = "https://example.com"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: t_algebra
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - aaron
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-12-06 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - afg419@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rspec"
22
+ - ".travis.yml"
23
+ - CODE_OF_CONDUCT.md
24
+ - Gemfile
25
+ - Gemfile.lock
26
+ - LICENSE.txt
27
+ - README.md
28
+ - Rakefile
29
+ - bin/console
30
+ - bin/setup
31
+ - lib/t_algebra.rb
32
+ - lib/t_algebra/applicative.rb
33
+ - lib/t_algebra/functor.rb
34
+ - lib/t_algebra/monad.rb
35
+ - lib/t_algebra/monad/either.rb
36
+ - lib/t_algebra/monad/list.rb
37
+ - lib/t_algebra/monad/maybe.rb
38
+ - lib/t_algebra/monad/parser.rb
39
+ - lib/t_algebra/monad/reader.rb
40
+ - lib/t_algebra/version.rb
41
+ - t_algebra.gemspec
42
+ homepage: https://github.com/afg419/t_algebra
43
+ licenses:
44
+ - MIT
45
+ metadata:
46
+ allowed_push_host: https://rubygems.org
47
+ homepage_uri: https://github.com/afg419/t_algebra
48
+ source_code_uri: https://github.com/afg419/t_algebra
49
+ changelog_uri: https://example.com
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: 2.3.0
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubygems_version: 3.1.6
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Functor, Applicative, and Monad interfaces for Ruby
69
+ test_files: []