affect 0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0ef9c069aa2f0077d7cbf108e9f9a36aaf2a1808fdc1636d37e40333b6cfc2e0
4
+ data.tar.gz: 54e9f068ed3fecdb8dd624efb41b9540ecb1259b569636fca193a7c0fc27e172
5
+ SHA512:
6
+ metadata.gz: d2aadb70ccf3ea71c0dfa794cbe33424a4b2ab74ec6208d0137e3664149320a748199dc6c4944dd677ed3f4ace979e6411878165918e4bbd68c539eadbaa02cc
7
+ data.tar.gz: 983d269425f777d7aae8ce64251b293a170a8dc5dfd7a57a46bc0827e7e81c0bfaadd3111d9e578a15676d0621526278054a17c06e6b7c7bd098f2f8d26dfc66
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,19 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ affect (0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ minitest (5.11.3)
10
+
11
+ PLATFORMS
12
+ ruby
13
+
14
+ DEPENDENCIES
15
+ affect!
16
+ minitest (= 5.11.3)
17
+
18
+ BUNDLED WITH
19
+ 1.17.2
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Sharon Rosner
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 all
13
+ 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 THE
21
+ SOFTWARE.
@@ -0,0 +1,300 @@
1
+ # Affect - structured side effects for functional Ruby
2
+
3
+ [INSTALL](#installing-affect) |
4
+ [TUTORIAL](#getting-started) |
5
+ [EXAMPLES](examples) |
6
+
7
+ > Affect | əˈfɛkt | verb [with object] have an effect on; make a difference to.
8
+
9
+ ## What is Affect
10
+
11
+ Affect is a tiny Ruby gem providing a way to isolate and handle side-effects in
12
+ functional programs. Affect implements algebraic effects in Ruby, but can also
13
+ be used to implement patterns that are orthogonal to object-oriented
14
+ programming, such as inversion of control and dependency injection.
15
+
16
+ > **Note**: Affect does not pretend to be a *complete, theoretically correct*
17
+ > implementation of algebraic effects. Affect concentrates on the idea of
18
+ > [effect contexts](#the-effect-context). It does not deal with continuations,
19
+ > asynchrony, or any other concurrency constructs.
20
+
21
+ ## Installing Affect
22
+
23
+ ```bash
24
+ $ gem install affect
25
+ ```
26
+
27
+ Or add it to your Gemfile, you know the drill.
28
+
29
+ ## Getting Started
30
+
31
+ Algebraic effects introduces the concept of effect handlers, little pieces of
32
+ code that are provided by the caller, and invoked by the callee using a uniform
33
+ interface. An example of algebraic effects might be logging. Normally, if we
34
+ wanted to log a certain message to `STDOUT` or to a file, we wold do the
35
+ following:
36
+
37
+ ```ruby
38
+ def mul(x, y)
39
+ # assume LOG is a global logger object
40
+ LOG.info("called with #{x}, #{y}")
41
+ x * y
42
+ end
43
+
44
+ puts "Result: #{ mul(2, 3) }"
45
+ ```
46
+
47
+ The act of logging is a side-effect of our computation. We need to have a global
48
+ `LOG` object, and we cannot test the functioning of the `mul` method in
49
+ isolation. What if we wanted to be able to plug-in a custom logger, or intercept
50
+ calls to the logger?
51
+
52
+ Affect provides a solution for such problems by implementing a uniform,
53
+ composable interface for isolating and handling side effects:
54
+
55
+ ```ruby
56
+ require 'affect'
57
+
58
+ def mul(x, y)
59
+ # assume LOG is a global logger object
60
+ Affect :log, "called with #{x}, #{y}"
61
+ x * y
62
+ end
63
+
64
+ Affect.wrap {
65
+ puts "Result: #{ mul(2, 3) }"
66
+ }.on(:log) { |message|
67
+ puts "#{Time.now} #{message} (this is a log message)"
68
+ }.()
69
+ ```
70
+
71
+ In the example above, we replace the call to `LOG.info` with an invocation of a
72
+ `LogIntent` instance. When the intent is passed to `Affect`, the corresponding
73
+ handler is called in order to perform the intent.
74
+
75
+ In essence, by separating the performance of side effects into effect intents,
76
+ and effect handlers, we have separated the what from the how. The `mul` method
77
+ is no longer concerned with how to log the message it needs to log. There's no
78
+ hardbaked reference to a `Log` object, and no logging API to follow. Instead,
79
+ the *intent* to log a message is passed on to `Affect`, which in turn runs the
80
+ correct handler that actually does the logging.
81
+
82
+ ## Performing side effects
83
+
84
+ Side effects are performed by calling `Affect.perform` or simply `Affect()` with
85
+ a specification of the effect to be performed:
86
+
87
+ ```ruby
88
+ Affect.perform :foo
89
+
90
+ # or:
91
+ Affect :foo
92
+ ```
93
+
94
+ You can also pass along more arguments. Those will in turn be passed to the
95
+ effect handler:
96
+
97
+ ```ruby
98
+ Affect :log, 'my message'
99
+ ```
100
+
101
+ Effects can be represented using any Ruby object, but in a relatively complex
102
+ application might be best represented using classes or structs signifying the
103
+ *intent* to perform an effect:
104
+
105
+ ```ruby
106
+ LogIntent = Struct.new(:msg)
107
+
108
+ Affect LogIntent.new('my message')
109
+ ```
110
+
111
+ When representing effects using symbols, Affect provides a shorthand way to
112
+ perform effects by calling methods directly on the `Affect` module:
113
+
114
+ ```ruby
115
+ Affect.log('my message')
116
+ ```
117
+
118
+ Finally, effects should be performed inside of an effect context, by invoking
119
+ `Affect::Context#call`. Mostly, you'll want to use the callable shorthand
120
+ `.() { ... }`:
121
+
122
+ ```ruby
123
+ def add(x, y)
124
+ Affect :log, "adding #{x} and #{y}..."
125
+ x + y
126
+ end
127
+
128
+ Affect.on(:log) { |msg| puts "#{Time.now} #{msg}" }.() {
129
+ result = add(2, 2)
130
+ puts "result: #{result}"
131
+ }
132
+ ```
133
+
134
+ ## handling side effects
135
+
136
+ Side effect handlers can be defined using the `Affect.on` or `Affect.handle`
137
+ methods. `Affect.on` is used to register one or more effect handlers:
138
+
139
+ ```ruby
140
+ # register a single effect handler
141
+ Affect.on(:log) { |msg| puts "#{Time.now} #{msg}" }
142
+
143
+ # register multiple effect handlers by passing in a hash
144
+ Affect.on(
145
+ log: ->(msg) { puts "#{Time.now} #{msg}" },
146
+ ask: -> { gets.chomp }
147
+ )
148
+ ```
149
+
150
+ `Affect.handle` is used as a catch-all handler:
151
+
152
+ ```ruby
153
+ Affect.handle do |effect, *args|
154
+ case effect
155
+ when :log then puts "#{Time.now} #{msg}"
156
+ when :ask then gets.chomp
157
+ end
158
+ end
159
+ ```
160
+
161
+ Note that when `Affect.handle` is used to handle effects, no error will be
162
+ raised for unhandled effects.
163
+
164
+ ## The effect context
165
+
166
+ Affect defines an effect context which is unique to *each thread or fiber*.
167
+ Effect contexts can be thought of as stack frames containing information about
168
+ effect handlers. When effects are invoked using method calls on `Affect`, the
169
+ call is routed to the most current effect context. If the current effect context
170
+ does not know how to handle a certain effect, the call will bubble up the stack
171
+ of effect contexts until a handler is found:
172
+
173
+ ```ruby
174
+ # First effect context
175
+ Affect.on(:log) { |msg| LOG.info(msg) },
176
+ ).() {
177
+ log("starting")
178
+ # Second effect context
179
+ Affect.on(
180
+ log: ->(*args) { }
181
+ ).() {
182
+ log("this message will not be logged")
183
+ }
184
+ log("stopping")
185
+ }
186
+ ```
187
+
188
+ ## Putting it all together
189
+
190
+ The Affect API uses method chaining to add effect handlers and finally, execute
191
+ the application code. Multiple effect handlers can be chained as follows:
192
+
193
+ ```ruby
194
+ Affect
195
+ .on(:ask) { ... }
196
+ .on(:tell) { ... }
197
+ .() do
198
+ ...
199
+ end
200
+ ```
201
+
202
+ ## Other usages
203
+
204
+ ### Dependency injection
205
+
206
+ Affect can also be used for dependency injection. Dependencies can be injected
207
+ by providing effect handlers:
208
+
209
+ ```ruby
210
+ Affect.on(:db) {
211
+ get_db_connection
212
+ }.() {
213
+ process_users(Affect.db.query('select * from users'))
214
+ }
215
+ ```
216
+
217
+ This is especially useful for testing purposes as described below:
218
+
219
+ ### Testing
220
+
221
+ One particular benefit of using Affect is the way it facilitates testing. When
222
+ mutable state and side-effects are pulled out of methods and into effect
223
+ handlers, testing becomes much easier. Side effects can be mocked or tested
224
+ in isolation, and dependencies provided through effect handlers can also be
225
+ mocked. The following section includes an example of testing with algebraic
226
+ effects.
227
+
228
+ ## Writing applications using algebraic effects
229
+
230
+ Algebraic effects have yet to be adopted by any widely used programming
231
+ language, and they remain a largely theoretical subject in computer science.
232
+ Their advantages are still to be proven in actual usage. We might discover that
233
+ they're completely inadequate as a solution for managing side-effects, or we
234
+ might discover new techniques to be used in conjunction with algebraic effects.
235
+
236
+ One important principle to keep in mind is that in order to make the best of
237
+ algebraic effects, effect handlers need to be pushed to the outside of your
238
+ code. In most cases, the effect context will be defined in the entry-point of
239
+ your program, rather than somewhere on the inside.
240
+
241
+ Imagine a program that counts the occurences of a user-defined pattern in a
242
+ given text file:
243
+
244
+ ```ruby
245
+ require 'affect'
246
+
247
+ def pattern_count(pattern)
248
+ total_count = 0
249
+ found_count = 0
250
+ while (line = Affect.gets)
251
+ total_count += 1
252
+ found_count += 1 if line =~ pattern
253
+ end
254
+ Affect.log "found #{found_count} occurrences in #{total_count} lines"
255
+ found_count
256
+ end
257
+
258
+ Affect.on(
259
+ gets: -> { Kernel.gets },
260
+ log: -> { |msg| STDERR << "#{Time.now} #{msg}" }
261
+ ).() {
262
+ pattern = /#{ARGV[0]}/
263
+ count = pattern_count(pattern)
264
+ puts count
265
+ }
266
+ ```
267
+
268
+ In the above example, the `pattern_count` method, which does the "hard work",
269
+ communicates with the outside world through Affect in order to:
270
+
271
+ - read a line after line from some input stream
272
+ - log an informational message
273
+
274
+ Note that `pattern_count` does *not* deal directly with I/O. It does so
275
+ exclusively through Affect. Testing the method would be much simpler:
276
+
277
+ ```ruby
278
+ require 'minitest'
279
+ require 'affect'
280
+
281
+ class PatternCountTest < Minitest::Test
282
+ def test_correct_count
283
+ text = StringIO.new("foo\nbar")
284
+
285
+ Affect.on(:gets) { text.gets }.on(:log) { |msg| } # ignore
286
+ .() {
287
+ count = pattern_count(/foo/)
288
+ assert_equal(1, count)
289
+ }
290
+ end
291
+ end
292
+ ```
293
+
294
+ ## Contributing
295
+
296
+ Affect is a very small library designed to do very little. If you find it
297
+ compelling, have encountered any problems using it, or have any suggestions for
298
+ improvements, please feel free to contribute issues or pull requests.
299
+
300
+ ##
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ require_relative './lib/affect/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'affect'
6
+ s.version = Affect::VERSION
7
+ s.licenses = ['MIT']
8
+ s.summary = 'Affect: Algebraic Effects for Ruby'
9
+ s.author = 'Sharon Rosner'
10
+ s.email = 'ciconia@gmail.com'
11
+ s.files = `git ls-files`.split
12
+ s.homepage = 'http://github.com/digital-fabric/affect'
13
+ s.metadata = {
14
+ "source_code_uri" => "https://github.com/digital-fabric/affect"
15
+ }
16
+ s.rdoc_options = ["--title", "affect", "--main", "README.md"]
17
+ s.extra_rdoc_files = ["README.md"]
18
+ s.require_paths = ["lib"]
19
+
20
+ # s.add_runtime_dependency 'modulation', '~>0.25'
21
+
22
+ s.add_development_dependency 'minitest', '5.11.3'
23
+ end
@@ -0,0 +1,23 @@
1
+ require 'bundler/setup'
2
+ require 'affect'
3
+
4
+ def fact(x)
5
+ Affect :log, "calculating factorial for #{x}"
6
+ (x <= 1) ? 1 : x * fact(x - 1)
7
+ end
8
+
9
+ def main
10
+ Affect :prompt
11
+ x = Affect :input
12
+ result = fact(x)
13
+ Affect :output, "The factorial of result is #{result}"
14
+ end
15
+
16
+ ctx = Affect.on(
17
+ prompt: -> { puts "Enter a number: " },
18
+ input: -> { gets.chomp.to_i },
19
+ output: ->(msg) { puts msg },
20
+ log: ->(msg) { puts "#{Time.now} #{msg}" }
21
+ )
22
+
23
+ ctx.() { loop { main } }
@@ -0,0 +1,16 @@
1
+ require 'bundler/setup'
2
+ require 'affect'
3
+
4
+ def main
5
+ Affect :prompt
6
+ name = Affect :input
7
+ Affect :output, "Hi, #{name}! I'm Affected Ruby!"
8
+ end
9
+
10
+ ctx = Affect.on(
11
+ prompt: -> { puts "Enter your name: " },
12
+ input: -> { gets.chomp },
13
+ output: ->(msg) { puts msg }
14
+ )
15
+
16
+ ctx.() { main }
@@ -0,0 +1,14 @@
1
+ require 'bundler/setup'
2
+ require 'affect'
3
+
4
+ def mul(x, y)
5
+ # assume LOG is a global logger object
6
+ Affect :log, "called with #{x}, #{y}"
7
+ x * y
8
+ end
9
+
10
+ Affect.run {
11
+ puts "Result: #{ mul(2, 3) }"
12
+ }.on(:log) { |message|
13
+ puts "#{Time.now} #{message} (this is a log message)"
14
+ }.()
@@ -0,0 +1,22 @@
1
+ require 'bundler/setup'
2
+ require 'affect'
3
+
4
+ def pattern_count(pattern)
5
+ total_count = 0
6
+ found_count = 0
7
+ while (line = Affect.gets)
8
+ total_count += 1
9
+ found_count += 1 if line =~ pattern
10
+ end
11
+ Affect.log "found #{found_count} occurrences in #{total_count} lines"
12
+ found_count
13
+ end
14
+
15
+ Affect.on(
16
+ gets: -> { STDIN.gets },
17
+ log: ->(msg) { STDERR.puts "#{Time.now} #{msg}" }
18
+ ).() {
19
+ pattern = /#{ARGV[0]}/
20
+ count = pattern_count(pattern)
21
+ puts count
22
+ }
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Affect module
4
+ module Affect
5
+ Abort = Object.new # Used as an abort intent
6
+
7
+ # Effect context
8
+ class Context
9
+ def initialize(&block)
10
+ @closure = block
11
+ @handlers = {}
12
+ end
13
+
14
+ def on(effect, &block)
15
+ if effect.is_a?(Hash)
16
+ @handlers.merge!(effect)
17
+ else
18
+ @handlers[effect] = block
19
+ end
20
+ self
21
+ end
22
+
23
+ def handle(&block)
24
+ @handlers[nil] = block
25
+ self
26
+ end
27
+
28
+ def perform(effect, *args)
29
+ if (handler = find_handler(effect))
30
+ call_handler(handler, effect, *args)
31
+ elsif @parent_context
32
+ @parent_context.perform(effect, *args)
33
+ else
34
+ raise "No effect handler for #{effect.inspect}"
35
+ end
36
+ end
37
+
38
+ def find_handler(effect)
39
+ @handlers[effect] || @handlers[effect.class] || @handlers[nil]
40
+ end
41
+
42
+ def call_handler(handler, effect, *args)
43
+ if handler.arity == 0
44
+ handler.call
45
+ elsif args.empty?
46
+ handler.call(effect)
47
+ else
48
+ handler.call(*args)
49
+ end
50
+ end
51
+
52
+ def abort!(value = nil)
53
+ throw Abort, (value || Abort)
54
+ end
55
+
56
+ def call(&block)
57
+ current_thread = Thread.current
58
+ @parent_context = current_thread[:__affect_context__]
59
+ current_thread[:__affect_context__] = self
60
+ catch(Abort) do
61
+ (block || @closure).call
62
+ end
63
+ ensure
64
+ current_thread[:__affect_context__] = @parent_context
65
+ end
66
+ end
67
+
68
+ class << self
69
+ def wrap(&block)
70
+ Context.new(&block)
71
+ end
72
+
73
+ def call(&block)
74
+ Context.new(&block).call
75
+ end
76
+
77
+ def on(effect, &block)
78
+ Context.new.on(effect, &block)
79
+ end
80
+
81
+ def handle(&block)
82
+ Context.new.handle(&block)
83
+ end
84
+
85
+ def current_context
86
+ Thread.current[:__affect_context__] || (raise 'No effect context present')
87
+ end
88
+
89
+ def perform(effect, *args)
90
+ current_context.perform(effect, *args)
91
+ end
92
+
93
+ alias_method :method_missing, :perform
94
+
95
+ def abort!(value = nil)
96
+ current_context.abort!(value)
97
+ end
98
+ end
99
+ end
100
+
101
+ # Kernel extension
102
+ module Kernel
103
+ def Affect(effect, *args)
104
+ Affect.current_context.perform(effect, *args)
105
+ end
106
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Affect
4
+ VERSION = '0.1'
5
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'bundler/setup'
5
+ require 'affect'
6
+
7
+ class AffectAPITest < Minitest::Test
8
+ def test_that_perform_raises_on_no_handler
9
+ assert_raises RuntimeError do
10
+ Affect.wrap {
11
+ Affect.perform :foo
12
+ }.on(:bar) {
13
+ :baz
14
+ }.()
15
+ end
16
+
17
+ # using method call on Affect
18
+ assert_raises RuntimeError do
19
+ Affect.wrap {
20
+ Affect.foo
21
+ }.()
22
+ end
23
+
24
+ # no raise
25
+ Affect.wrap {
26
+ Affect.perform :foo
27
+ }.on(:foo) {
28
+ :bar
29
+ }.()
30
+ end
31
+
32
+ def test_that_emitted_effect_is_performed
33
+ counter = 0
34
+ Affect.wrap {
35
+ 3.times { Affect.perform :incr }
36
+ }.on(:incr) {
37
+ counter += 1
38
+ }.()
39
+
40
+ assert_equal(3, counter)
41
+ end
42
+
43
+ def test_that_api_methods_return_context
44
+ o = Affect.wrap {
45
+ Affect.perform :foo
46
+ }
47
+ assert_kind_of(Affect::Context, o)
48
+
49
+ o = Affect.on(:foo) {
50
+ :bar
51
+ }
52
+ assert_kind_of(Affect::Context, o)
53
+
54
+ o = Affect.handle { }
55
+ assert_kind_of(Affect::Context, o)
56
+ end
57
+
58
+ def test_that_contexts_can_be_nested
59
+ results = []
60
+ o = Affect.wrap {
61
+ Affect.perform :foo
62
+ Affect.perform :bar
63
+
64
+ Affect.wrap {
65
+ Affect.perform :foo
66
+ Affect.perform :bar
67
+ }
68
+ .on(:bar) { results << :baz }
69
+ .()
70
+ }
71
+ .on(:foo) { results << :foo }
72
+ .on(:bar) { results << :bar }
73
+ .()
74
+
75
+ assert_equal([:foo, :bar, :foo, :baz], results)
76
+ end
77
+
78
+ def test_that_effects_can_be_emitted_as_method_calls_on_Affect
79
+ results = []
80
+ o = Affect.wrap {
81
+ Affect.foo
82
+ Affect.bar
83
+
84
+ Affect.wrap {
85
+ Affect.foo
86
+ Affect.bar
87
+ }
88
+ .on(:bar) { results << :baz }
89
+ .()
90
+ }
91
+ .on(:foo) { results << :foo }
92
+ .on(:bar) { results << :bar }
93
+ .()
94
+
95
+ assert_equal([:foo, :bar, :foo, :baz], results)
96
+ end
97
+
98
+ def test_that_abort_can_be_called_from_wrapped_code
99
+ effects = []
100
+ Affect.handle { |o| effects << o }.() do
101
+ Affect 1
102
+ Affect.abort!
103
+ Affect 2
104
+ end
105
+
106
+ assert_equal([1], effects)
107
+ end
108
+
109
+ def test_that_abort_causes_call_to_return_optional_value
110
+ o = Affect.() { Affect.abort! }
111
+ assert_equal(Affect::Abort, o)
112
+
113
+ o = Affect.() { Affect.abort!(42) }
114
+ assert_equal(42, o)
115
+ end
116
+
117
+ class I1; end
118
+
119
+ class I2; end
120
+
121
+ def test_that_intent_instances_are_handled_correctly
122
+ results = []
123
+ Affect
124
+ .on(I1) { results << :i1 }
125
+ .on(I2) { results << :i2 }
126
+ .() {
127
+ Affect I1.new
128
+ Affect I2.new
129
+ }
130
+
131
+ assert_equal([:i1, :i2], results)
132
+ end
133
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: affect
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Sharon Rosner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-07-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 5.11.3
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 5.11.3
27
+ description:
28
+ email: ciconia@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files:
32
+ - README.md
33
+ files:
34
+ - ".gitignore"
35
+ - Gemfile
36
+ - Gemfile.lock
37
+ - LICENSE
38
+ - README.md
39
+ - affect.gemspec
40
+ - examples/fact.rb
41
+ - examples/greet.rb
42
+ - examples/logging.rb
43
+ - examples/pat.rb
44
+ - lib/affect.rb
45
+ - lib/affect/version.rb
46
+ - test/test_affect.rb
47
+ homepage: http://github.com/digital-fabric/affect
48
+ licenses:
49
+ - MIT
50
+ metadata:
51
+ source_code_uri: https://github.com/digital-fabric/affect
52
+ post_install_message:
53
+ rdoc_options:
54
+ - "--title"
55
+ - affect
56
+ - "--main"
57
+ - README.md
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubygems_version: 3.0.3
72
+ signing_key:
73
+ specification_version: 4
74
+ summary: 'Affect: Algebraic Effects for Ruby'
75
+ test_files: []