affect 0.1

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