result-monad 0.1.4 → 0.2.0

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
  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