result-monad 0.1.4 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d7a2508b248f8d999b8b3c4566001f102399d07e
4
- data.tar.gz: e1243b101da496354f6e6bd0aaedbd0397ab559f
3
+ metadata.gz: a15b6b9f028996e96deaa63cc18edb7e314e1043
4
+ data.tar.gz: 7a45714caaf085a3c3d11ad4b08525758dcf2daa
5
5
  SHA512:
6
- metadata.gz: a31672ae7159a4257786b74a766aaba389ff2050dfdce92f5ed555e388c3f13c2a34dff8ca18b915f7db6cee271d4b13ab80c329c67800b1a39d4dfec21275ac
7
- data.tar.gz: b877510e55b8460dd0a60f45318c99d03ff916829655f1b6cf8129a6c114b3136eebd59ff9724bc6f3d632bbc2a769ed2a9ef167b7deac3f950251d5eb30e20b
6
+ metadata.gz: d80f7dbfaedf0fb7ebf4bf1d483757d792d455f572f4bb51a0f29a9b1bead9924a16f2178e66e69fef24ab2f0647716cee783c6990189331dbbbf2083ab7219b
7
+ data.tar.gz: a10d6be8ae576889ea88c4190f7784974de3c372c61b20eb49fdc9289e2b2e977cb161e952f1f8e0c9a06ab9b21b52cc1bf203c1378dc5e16433fe397b432875
data/README.md CHANGED
@@ -87,112 +87,6 @@ x.map {|i| i / 10}
87
87
  #=>Result<Error(divided by 0)>
88
88
  ```
89
89
 
90
- #### Performing actions in sequence
91
-
92
- ```ruby
93
- x = Ok(5)
94
- y = Error(99)
95
- z = Ok(25)
96
-
97
- # In this case, imagine x and z as successful actions, whereas y is an unsuccessful action.
98
-
99
- x & y & z #=> Result<Error(99)>
100
-
101
- ```
102
-
103
- #### Real World Scenario
104
-
105
- Lets say we are using an external library to read data from a file over FTP,
106
- write it to a local file, and then delete the file on the FTP server. Some things to note:
107
-
108
- * We use overloaded operator & to chain on Ok
109
- * Every call to any outside library, even standard library functions are either in a `map` block which
110
- gets Captured or they are themselves Captured
111
- * No independent action (as in someone calling `copy_file` directly) will execute without its requirements being met.
112
- * A shortcut to creating an Error value based on some condition, as well as capturing errors generated from testing the
113
- condition can be seen in `ensure_file`. Any exceptions raised by File.exist?, as well as our condition not being met
114
- are captured as Error
115
-
116
- ```ruby
117
- require 'net/ftp'
118
-
119
- class SensitiveFileFTP
120
- def initialize(server_uri)
121
- @server_uri = server_uri
122
- end
123
-
124
- def transfer_and_delete(file_name)
125
- login & # "&" chains on Ok
126
- copy_file(file_name) &
127
- ensure_file(file_name) &
128
- delete_remote_file(file_name)
129
- end
130
-
131
- def ftp
132
- @ftp ||= Capture { Net::FTP.new(@server_uri) }
133
- end
134
-
135
- def login
136
- ftp.map(&:login)
137
- end
138
-
139
- def copy_file(source)
140
- ftp.map { |f| f.getbinaryfile(source) }
141
- end
142
-
143
- def ensure_file(name)
144
- Capture { raise "Can not verify local copy of file" unless File.exist?(name) }
145
- end
146
-
147
- def delete_remote_file(name)
148
- ftp.map {|f| f.delete(name) }
149
- end
150
-
151
- def delete_local_file(name)
152
- Capture { File.delete(name) }
153
- end
154
- end
155
-
156
- ftp = SensitiveFileFTP.new('speedtest.tele2.net')
157
- ftp.transfer_and_delete("5MB.zip")
158
- ```
159
-
160
- `transfer_and_delete` can return numerous errors but there is no way for this code to raise an exception. Exceptions
161
- raised in `map` or `map!` are caught and expressed as `Error`.
162
-
163
- #### Conditionally executing code
164
-
165
- Using the example above, say we wanted to find and delete the file if we may have accidentally created it.
166
- This wouldn't be a sensible way of solving this problem, but this illustrates a use case for result base conditionals.
167
-
168
- ```ruby
169
- ftp = SensitiveFileFTP.new('speedtest.tele2.net')
170
- ftp.transfer_and_delete("5MB.zip").or_else do
171
- ftp.delete_local_file("5MB.zip")
172
- end
173
-
174
- # or lets say we wanted to log a success if it worked
175
-
176
- ftp.transfer_and_delete("5MB.zip").and_then do
177
- log "Did the thing!"
178
- end
179
- ```
180
-
181
-
182
- ## What's next
183
-
184
- * More documentation and refined use cases for existing methods, like `map_err`, `map!`, and `|`
185
-
186
- * refine methods of conditionally executing blocks. Should the return values be wrapped in results, or should we trust
187
- the users to do it?
188
-
189
- * Right now, the only computational state kept is the last good result or the first error that occurred.
190
- This may not be ideal or appropriate. We also don't join errors up the chain, so it is possible to end up with
191
- something like this: `Error(Error(Error(Error(Error(...)))))`. We either need to find a way to join, and perhaps
192
- store the errors as some aggregate object, or we need to provide utilities for probing for information on the different
193
- nested errors. I think it may be appropriate to kep a collection of errors, and ensure that we don't nest.
194
-
195
-
196
90
  ## Development
197
91
 
198
92
  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.
data/lib/result-monad.rb CHANGED
@@ -1,156 +1,30 @@
1
- require "result-monad/version"
2
- require 'byebug'
1
+ require 'result-monad/version'
2
+ require 'result-monad/result_error'
3
+ require 'result-monad/result_methods'
4
+ require 'result-monad/error'
5
+ require 'result-monad/ok'
3
6
 
4
7
  def Error(error)
5
- Result.error(error)
8
+ Error.new(error)
6
9
  end
7
10
 
8
11
  def Ok(value)
9
- Result.ok(value)
12
+ Ok.new(value)
10
13
  end
11
14
 
12
- def Capture
13
- begin
14
- Ok(yield).join!
15
- rescue StandardError => e
16
- Error(e).join!
15
+ def Test(o, e)
16
+ if yield
17
+ Ok(o)
18
+ else
19
+ Error(e)
17
20
  end
18
21
  end
19
22
 
20
- class Result
21
- require 'result-monad/error'
22
- require 'result-monad/ok'
23
-
24
- def initialize(m)
25
- @m = m
26
- end
27
-
28
- def ok?
29
- @m.is_a? Ok
30
- end
31
-
32
- def error?
33
- @m.is_a? Error
34
- end
35
-
36
- def map!(&block)
37
- if ok?
38
- @m.map!(&block)
39
- self.lift!(val)
40
- else
41
- self
42
- end
43
- end
44
-
45
- def map_err!(&block)
46
- if error?
47
- @m.map!(&block)
48
- else
49
- self
50
- end
51
- end
52
-
53
- def map(&block)
54
- if ok?
55
- @m.map(&block)
56
- else
57
- self
58
- end
59
- end
60
-
61
- def map_err(&block)
62
- if error?
63
- @m.map(&block)
64
- else
65
- self
66
- end
67
- end
68
-
69
- def join!
70
- if ok? && val.is_a?(Result)
71
- lift!(val)
72
- end
73
- self
74
- end
75
-
76
- def lift!(other)
77
- @m = (self & other.join!).instance_variable_get(:"@m")
78
- self
79
- end
80
-
81
- def unwrap
82
- raise ResultError.new(self), "Attempted to unwrap an error" if @m.is_a? Error
83
- val
84
- end
85
-
86
- def and(other)
87
- error? ? self : other
88
- end
89
- alias_method :&, :and
90
-
91
- def or(other)
92
- error? ? other : self
93
- end
94
- alias_method :|, :or
95
-
96
- def and_then(&block)
97
- Capture {block.call} if ok?
98
- end
99
-
100
- def or_else(&block)
101
- Capture {block.call} if error?
102
- end
103
-
104
- def unwrap_or(cons)
105
- ok? ? unwrap : cons
106
- end
107
-
108
- def map_when(kind, &block)
109
- @m.map(&block) if val.is_a?(kind)
110
- self
111
- end
112
-
113
- def do_when(kind, &block)
114
- Capture { block.call } if val.is_a?(kind)
115
- self
116
- end
117
-
118
- def raise_or_return!
119
- if ok?
120
- return val
121
- else
122
- raise val
123
- end
124
- end
125
-
126
- # Constructors
127
- def self.error(error)
128
- new(Error.new(error))
129
- end
130
-
131
- def self.ok(result)
132
- new(Ok.new(result))
133
- end
134
-
135
- def to_s
136
- @m.to_s
137
- end
138
-
139
- def inspect
140
- "Result<#{self}>"
141
- end
142
-
143
- private
144
-
145
- def val
146
- @m.instance_variable_get(:"@value")
23
+ def Capture
24
+ begin
25
+ Ok(yield)
26
+ rescue StandardError => e
27
+ Error(e)
147
28
  end
148
29
  end
149
30
 
150
- class ResultError < StandardError
151
- attr_reader :result
152
-
153
- def initialize(result)
154
- @result = result
155
- end
156
- end
@@ -1,27 +1,13 @@
1
1
  class Error
2
- def initialize(value)
3
- @value = value
4
- end
5
2
 
6
- def map!(&block)
7
- @value = Capture {block.call(@value)}
8
- self
9
- end
3
+ include ResultMethods
10
4
 
11
- def map(&block)
12
- capture_as_error { block.call(@value) }
5
+ def initialize(err)
6
+ @err = err
13
7
  end
14
8
 
15
- def to_s
16
- "Error(#{@value})"
9
+ def handle_result(success_handler, failure_handler)
10
+ failure_handler.call(@err)
17
11
  end
18
12
 
19
- private
20
- def capture_as_error
21
- begin
22
- Error(yield)
23
- rescue StandardError => e
24
- Error(e)
25
- end
26
- end
27
13
  end
@@ -1,18 +1,13 @@
1
1
  class Ok
2
- def initialize(value)
3
- @value = value
4
- end
5
2
 
6
- def map!(&block)
7
- @value = Capture { block.call(@value) }
8
- self
9
- end
3
+ include ResultMethods
10
4
 
11
- def map(&block)
12
- Capture { block.call(@value) }
5
+ def initialize(result)
6
+ @result = result
13
7
  end
14
8
 
15
- def to_s
16
- "Ok(#{@value})"
9
+ def handle_result(success_handler, failure_handler)
10
+ success_handler.call(@result)
17
11
  end
12
+
18
13
  end
@@ -0,0 +1,2 @@
1
+ class ResultError < StandardError
2
+ end
@@ -0,0 +1,79 @@
1
+ module ResultMethods
2
+ #TODO:
3
+ # map! Not necessarily a good idea
4
+ # map_unsafe! See above
5
+ def map
6
+ handle_result(
7
+ Proc.new {|val| Capture { yield(val) } },
8
+ Proc.new {|err| self }
9
+ )
10
+ end
11
+
12
+ def map_err
13
+ handle_result(
14
+ Proc.new {|val| self },
15
+ Proc.new {|val| Capture { yield(val) } }
16
+ )
17
+ end
18
+
19
+ def join
20
+ handle_result(
21
+ Proc.new { |x| x.is_a?(Ok) || x.is_a?(Error) ? x.join : self },
22
+ Proc.new { |x| self }
23
+ )
24
+ end
25
+
26
+ def flat_map(&block)
27
+ (map &block).join
28
+ end
29
+
30
+ def map_unsafe
31
+ handle_result(
32
+ Proc.new {|val| yield(val) },
33
+ Proc.new {|err| self }
34
+ )
35
+ end
36
+
37
+ def and_then
38
+ handle_result(
39
+ Proc.new {|val| yield(val); self},
40
+ Proc.new {|err| self}
41
+ )
42
+ end
43
+
44
+ def or_else
45
+ handle_result(
46
+ Proc.new {|val| self},
47
+ Proc.new {|err| yield(val); self}
48
+ )
49
+ end
50
+
51
+ def flat_map_unsafe(&block)
52
+ (map_unsafe &block).join
53
+ end
54
+
55
+ def unwrap
56
+ handle_result(
57
+ Proc.new {|val| val},
58
+ Proc.new do |err|
59
+ if err.is_a? Exception
60
+ raise err
61
+ else
62
+ raise ResultError.new, "#{err}"
63
+ end
64
+ end
65
+ )
66
+ end
67
+
68
+ def ok?
69
+ is_a? Ok
70
+ end
71
+
72
+ def error?
73
+ is_a? Error
74
+ end
75
+
76
+ def to_s
77
+ "#{ self.class.name }: #{@result}"
78
+ end
79
+ end
@@ -1,3 +1,3 @@
1
1
  class Result
2
- VERSION = "0.1.4"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: result-monad
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zachary Daniel
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-02-01 00:00:00.000000000 Z
11
+ date: 2016-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -88,6 +88,8 @@ files:
88
88
  - lib/result-monad.rb
89
89
  - lib/result-monad/error.rb
90
90
  - lib/result-monad/ok.rb
91
+ - lib/result-monad/result_error.rb
92
+ - lib/result-monad/result_methods.rb
91
93
  - lib/result-monad/version.rb
92
94
  - result-monad.gemspec
93
95
  homepage: http://gitlab.com/zachdaniel/result-monad