dry-monads 1.0.0.beta1 → 1.0.0.beta2

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
  SHA256:
3
- metadata.gz: e39ab380c859836fb98aad8bc158eeafb73896472cc703254c0f21490c6cde68
4
- data.tar.gz: 749f9cf55040d0cafc8f8f4e3d8c60a8e2486768223069adff7f0b4c55cc9a43
3
+ metadata.gz: 9b72d0fd34804b1fb9f751b61ffe8799386c3836a90625266948469439f576ad
4
+ data.tar.gz: 3303c72e17d0e905a8ac036c9ebd316fdfcbf6414ab3473d5c18a70ddf5fba01
5
5
  SHA512:
6
- metadata.gz: 3c66d5ea73e5c781e26acfb1611067a34cc25a9fbbb258a01ad8f5705b33cd569944ae9eab126e18456b4f6eeec0722163e39406e52c332e5224a78b5dae53e9
7
- data.tar.gz: 73a8d308f628a5ea630cabdaf240f67627e3310ec3cf64a75cec7140b6c5645853cfcf50ecc463a38f1813c28b78a145d7d40ec45998327c7cbedce1177c60c5
6
+ metadata.gz: d7ec39938788dcc849f49125404790685785e76a881624df0ab0dd6ff7a9d25d1af2938dfa1af9854cc6b007417c0079a1f6d378bd155704f980cce02a6c279a
7
+ data.tar.gz: 1b9c4604241164988bc65e5da83b7d33445fda6c7de4de8f93d04d4cf2ae3d0e3592d367961469fe6858f7f58c7b83a89c5a7de01cf99a8108b3294370804b07
data/CHANGELOG.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Added
4
4
 
5
- * `do`-like notation (the idea comes from Haskell of course). This is the biggest and most important addition in the release which greatly increases the ergonomics of using monads in Ruby. Basically, almost everything it does is passing a block to a given method. You call `yield` on monads to extract the values. If any operation fails i.e. no value can be extracted, the whole computation is halted and the failing step becomes a result. With `Do` you don't need to chain monadic values with `fmap/bind` and block, everything can be done on a single level of indentation. Here is a more or less real-life example:
5
+ * `do`-like notation (the idea comes from Haskell of course). This is the biggest and most important addition to the release which greatly increases the ergonomics of using monads in Ruby. Basically, almost everything it does is passing a block to a given method. You call `yield` on monads to extract the values. If any operation fails i.e. no value can be extracted, the whole computation is halted and the failing step becomes a result. With `Do` you don't need to chain monadic values with `fmap/bind` and block, everything can be done on a single level of indentation. Here is a more or less real-life example:
6
6
 
7
7
  ```ruby
8
8
  class CreateUser
@@ -42,7 +42,7 @@
42
42
  def create_user(user_data)
43
43
  Try[Sequel::Error] { user_repo.create(user_data) }.to_result
44
44
  end
45
-
45
+
46
46
  def create_profile(user, profile_data)
47
47
  Try[Sequel::Error] {
48
48
  user_repo.create_profile(user, profile_data)
@@ -53,7 +53,7 @@
53
53
 
54
54
  In the code above any `yield` can potentially fail and return the failure reason as a result. In other words, `yield None` acts as `return None`. Internally, `Do` uses exceptions, not `return`, this is somewhat slower but allows to detect failed operations in DB-transactions and roll back the changes which far more useful than an unjustifiable speed boost (flash-gordon)
55
55
 
56
- * The `Task` monad based on `Promise` from the [`concurrent-ruby` gem](https://github.com/ruby-concurrency/concurrent-ruby/). `Task` represents an asynchrounos computation which _can be_ (doesn't have to!) run on a seperated thread. `Promise` already offers a good API and implemented in a safe manner so `dry-monads` just adds a monad-compatible interface for it. Out of the box, `concurrent-ruby` has three types of executors for running blocks: `:io`, `:fast`, `:immediate`, check out [the docs](http://ruby-concurrency.github.io/concurrent-ruby/root/Concurrent.html#executor-class_method) for details. You can provide your own executor if needed (flash-gordon)
56
+ * The `Task` monad based on `Promise` from the [`concurrent-ruby` gem](https://github.com/ruby-concurrency/concurrent-ruby/). `Task` represents an asynchronous computation which _can be_ (doesn't have to!) run on a separated thread. `Promise` already offers a good API and implemented in a safe manner so `dry-monads` just adds a monad-compatible interface for it. Out of the box, `concurrent-ruby` has three types of executors for running blocks: `:io`, `:fast`, `:immediate`, check out [the docs](http://ruby-concurrency.github.io/concurrent-ruby/root/Concurrent.html#executor-class_method) for details. You can provide your own executor if needed (flash-gordon)
57
57
 
58
58
  ```ruby
59
59
  include Dry::Monads::Task::Mixin
@@ -61,7 +61,7 @@
61
61
  def call
62
62
  name = Task { get_name_via_http } # runs a request in the background
63
63
  email = Task { get_email_via_http } # runs another one request in the background
64
-
64
+
65
65
  # to_result forces both computations/requests to complete by pausing current thread
66
66
  # returns `Result::Success/Result::Failure`
67
67
  name.bind { |n| email.fmap { |e| create(e, n) } }.to_result
@@ -87,15 +87,15 @@
87
87
  list = List::Task[Task { get_name }, Task { get_email }]
88
88
  list.traverse # => Task(List['John', 'john@doe.org'])
89
89
  ```
90
-
90
+
91
91
  The code above runs two tasks in parallel and automatically combines their results with `traverse` (flash-gordon)
92
-
92
+
93
93
  * `Try` got a new call syntax supported in Ruby 2.5+
94
94
 
95
95
  ```ruby
96
- Try[ArgumentError, TypeError] { unsafe_operation }
96
+ Try[ArgumentError, TypeError] { unsafe_operation }
97
97
  ```
98
-
98
+
99
99
  Prior to 2.5, it wasn't possible to pass a block to `[]`.
100
100
 
101
101
  * The `Validated` “monad” that represents a result of a validation. Suppose, you want to collect all the errors and return them at once. You can't have it with `Result` because when you `traverse` a `List` of `Result`s it returns the first value and this is the correct behavior from the theoretical point of view. `Validated`, in fact, doesn't have a monad instance but provides a useful variant of applicative which concatenates the errors.
@@ -103,31 +103,41 @@
103
103
  ```ruby
104
104
  include Dry::Monads
105
105
  include Dry::Monads::Do.for(:call)
106
-
106
+
107
107
  def call(input)
108
108
  name, email = yield [
109
109
  validate_name(input[:name]),
110
110
  validate_email(input[:email])
111
111
  ]
112
-
112
+
113
113
  Success(create(name, email))
114
114
  end
115
-
116
- # can return
115
+
116
+ # can return
117
117
  # * Success(User(...))
118
118
  # * Invalid(List[:invalid_name])
119
- # * Invalid(List[:invalid_email])
120
- # * Invalid(List[:invalid_name, :invalid_email])
119
+ # * Invalid(List[:invalid_email])
120
+ # * Invalid(List[:invalid_name, :invalid_email])
121
121
  ```
122
-
123
- In the example above an array of `Validated` values is implicitly casted to `List::Validated`. It's supported because it's useful but don't forget it's all about types and don't mix different types of monads in a single array, the consequences are unclear. You always can be explicit with `List::Validated[validate_name(...), ...]`, choose what you like (flash-gordon).
124
-
125
- * `Failure`, `None`, and `Invalid` values now store the line where they were created. On of the biggest downside of dealing wtih monadic code is lack of backtraces. If you have a long list of computations and one of them fails how do you know where did it actually happen? Say, you've got `None` and this tells you nothing about _what variable_ was assigned to `None`. It makes sense to use `Result` instead of `Maybe` and use distinct errors everywhere but it doesn't always look good and forces you to think more. TLDR; call `.trace` to get the line where a fail-case was constructed
122
+
123
+ In the example above an array of `Validated` values is implicitly coerced to `List::Validated`. It's supported because it's useful but don't forget it's all about types so don't mix different types of monads in a single array, the consequences are unclear. You always can be explicit with `List::Validated[validate_name(...), ...]`, choose what you like (flash-gordon).
124
+
125
+ * `Failure`, `None`, and `Invalid` values now store the line where they were created. One of the biggest downsides of dealing with monadic code is lack of backtraces. If you have a long list of computations and one of them fails how do you know where did it actually happen? Say, you've got `None` and this tells you nothing about _what variable_ was assigned to `None`. It makes sense to use `Result` instead of `Maybe` and use distinct errors everywhere but it doesn't always look good and forces you to think more. TLDR; call `.trace` to get the line where a fail-case was constructed
126
126
 
127
127
  ```ruby
128
128
  Failure(:invalid_name).trace # => app/operations/create_user.rb:43
129
129
  ```
130
-
130
+
131
+ * `Dry::Monads::Unit` which can be used as a replacement for `Success(nil)` and in similar situations when you have side effects yet doesn't return anything meningful as a result. There's also the `.discard` method for mapping any successful result (i.e. `Success(?)`, `Some(?)`, `Value(?)`, etc) to `Unit`.
132
+
133
+ ```ruby
134
+ # we're making an HTTP request but "forget" any successful result,
135
+ # we only care if the task was complete without an error
136
+ Task { do_http_request }.discard
137
+ # ... wait for the task to finish ...
138
+ # => Task(valut=Unit)
139
+ ```
140
+
131
141
  ## Deprecations
132
142
 
133
143
  * `Either`, the former name of `Result`, is now deprecated
@@ -135,6 +145,7 @@
135
145
  ## BREAKING CHANGES
136
146
 
137
147
  * `Either#value` and `Maybe#value` were both droped, use `value_or` or `value!` when you :100: sure it's safe
148
+ * `require 'dry/monads'` doesn't load all monads anymore, use `require 'dry/monads/all'` instead or cherry pick them with `require 'dry/monads/maybe'` etc (timriley)
138
149
 
139
150
  [Compare v0.4.0...v1.0.0](https://github.com/dry-rb/dry-monads/compare/v0.4.0...master)
140
151
 
data/bin/console CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bundler/setup'
4
- require 'dry-monads'
4
+ require 'dry/monads/all'
5
5
 
6
6
  M = Dry::Monads
7
7
 
data/lib/dry/monads.rb CHANGED
@@ -1,87 +1,14 @@
1
- require 'dry/core/constants'
2
- require 'dry/monads/maybe'
3
- require 'dry/monads/try'
4
- require 'dry/monads/list'
5
- require 'dry/monads/task'
6
- require 'dry/monads/lazy'
7
- require 'dry/monads/result'
8
- require 'dry/monads/result/fixed'
9
- require 'dry/monads/do'
10
- require 'dry/monads/validated'
11
-
12
1
  module Dry
13
2
  # Common, idiomatic monads for Ruby
14
3
  #
15
4
  # @api public
16
5
  module Monads
17
- # @private
18
- Undefined = Dry::Core::Constants::Undefined
19
-
20
- # List of monad constructors
21
- CONSTRUCTORS = [
22
- Maybe::Mixin::Constructors,
23
- Result::Mixin::Constructors,
24
- Validated::Mixin::Constructors,
25
- Try::Mixin::Constructors,
26
- Task::Mixin::Constructors,
27
- Lazy::Mixin::Constructors
28
- ].freeze
29
-
30
- # @see Maybe::Some
31
- Some = Maybe::Some
32
- # @see Maybe::None
33
- None = Maybe::None
34
- # @see Result::Success
35
- Success = Result::Success
36
- # @see Result::Failure
37
- Failure = Result::Failure
38
- # @see Validated::Valid
39
- Valid = Validated::Valid
40
- # @see Validated::Invalid
41
- Invalid = Validated::Invalid
42
-
43
- extend(*CONSTRUCTORS)
44
-
45
- # @private
46
6
  def self.included(base)
47
- super
48
-
49
- base.include(*CONSTRUCTORS)
50
- end
51
-
52
- # Creates a module that has two methods: `Success` and `Failure`.
53
- # `Success` is identical to {Result::Mixin::Constructors#Success} and Failure
54
- # rejects values that don't conform the value of the `error`
55
- # parameter. This is essentially a Result type with the `Failure` part
56
- # fixed.
57
- #
58
- # @example using dry-types
59
- # module Types
60
- # include Dry::Types.module
61
- # end
62
- #
63
- # class Operation
64
- # # :user_not_found and :account_not_found are the only
65
- # # values allowed as failure results
66
- # Error =
67
- # Types.Value(:user_not_found) |
68
- # Types.Value(:account_not_found)
69
- #
70
- # def find_account(id)
71
- # account = acount_repo.find(id)
72
- #
73
- # account ? Success(account) : Failure(:account_not_found)
74
- # end
75
- #
76
- # def find_user(id)
77
- # # ...
78
- # end
79
- # end
80
- #
81
- # @param error [#===] the type of allowed failures
82
- # @return [Module]
83
- def self.Result(error, **options)
84
- Result::Fixed[error, **options]
7
+ if const_defined?(:CONSTRUCTORS)
8
+ base.include(*CONSTRUCTORS)
9
+ else
10
+ raise "Load all monads first with require 'dry/monads/all'"
11
+ end
85
12
  end
86
13
  end
87
14
  end
@@ -0,0 +1,26 @@
1
+ require 'dry/monads'
2
+ require 'dry/monads/do'
3
+ require 'dry/monads/lazy'
4
+ require 'dry/monads/list'
5
+ require 'dry/monads/maybe'
6
+ require 'dry/monads/result'
7
+ require 'dry/monads/result/fixed'
8
+ require 'dry/monads/task'
9
+ require 'dry/monads/try'
10
+ require 'dry/monads/validated'
11
+
12
+ module Dry
13
+ module Monads
14
+ # List of monad constructors
15
+ CONSTRUCTORS = [
16
+ Lazy::Mixin::Constructors,
17
+ Maybe::Mixin::Constructors,
18
+ Result::Mixin::Constructors,
19
+ Task::Mixin::Constructors,
20
+ Try::Mixin::Constructors,
21
+ Validated::Mixin::Constructors,
22
+ ].freeze
23
+
24
+ extend(*CONSTRUCTORS)
25
+ end
26
+ end
@@ -0,0 +1,31 @@
1
+ module Dry
2
+ module Monads
3
+ module ConversionStubs
4
+ def self.[](*method_names)
5
+ Module.new do
6
+ method_names.each do |name|
7
+ define_method(name) do |*|
8
+ Methods.public_send(name)
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ module Methods
15
+ module_function
16
+
17
+ def to_maybe
18
+ raise "Load Maybe first with require 'dry/monads/maybe'"
19
+ end
20
+
21
+ def to_result
22
+ raise "Load Result first with require 'dry/monads/result'"
23
+ end
24
+
25
+ def to_validated
26
+ raise "Load Validated first with require 'dry/monads/validated'"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/dry/monads/do.rb CHANGED
@@ -74,45 +74,58 @@ module Dry
74
74
  # @return [Module]
75
75
  def self.for(*methods)
76
76
  mod = Module.new do
77
- methods.each do |method|
78
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
79
- def #{ method }(*)
80
- super do |*ms|
81
- ms = coerce_to_monad(ms)
82
- unwrapped = ms.map { |m| m.to_monad.or { halt(m) }.value! }
83
- ms.size == 1 ? unwrapped[0] : unwrapped
84
- end
85
- rescue Halt => e
86
- e.result
87
- end
88
- RUBY
89
- end
77
+ methods.each { |m| Do.wrap_method(self, m) }
90
78
  end
91
79
 
92
80
  Module.new do
93
81
  singleton_class.send(:define_method, :included) do |base|
94
82
  base.prepend(mod)
95
83
  end
84
+ end
85
+ end
96
86
 
97
- def halt(result)
98
- raise Halt.new(result)
99
- end
87
+ protected
100
88
 
101
- # @private
102
- def coerce_to_monad(ms)
103
- return ms if ms.size != 1
89
+ # @private
90
+ def self.halt(result)
91
+ raise Halt.new(result)
92
+ end
104
93
 
105
- fst = ms[0]
94
+ # @private
95
+ def self.coerce_to_monad(ms)
96
+ return ms if ms.size != 1
106
97
 
107
- case fst
108
- when Array, List
109
- list = fst.is_a?(Array) ? List.coerce(fst) : fst
110
- [list.traverse]
98
+ fst = ms[0]
99
+
100
+ case fst
101
+ when Array, List
102
+ list = fst.is_a?(Array) ? List.coerce(fst) : fst
103
+ [list.traverse]
104
+ else
105
+ ms
106
+ end
107
+ end
108
+
109
+ # @private
110
+ def self.wrap_method(target, method)
111
+ target.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
112
+ def #{ method }(*)
113
+ if block_given?
114
+ super
111
115
  else
112
- ms
116
+ super do |*ms|
117
+ ms = Do.coerce_to_monad(ms)
118
+ unwrapped = ms.map { |r|
119
+ m = r.to_monad
120
+ m.or { Do.halt(m) }.value!
121
+ }
122
+ ms.size == 1 ? unwrapped[0] : unwrapped
123
+ end
113
124
  end
125
+ rescue Halt => e
126
+ e.result
114
127
  end
115
- end
128
+ RUBY
116
129
  end
117
130
  end
118
131
  end
@@ -0,0 +1,98 @@
1
+ require 'dry/monads/do'
2
+
3
+ module Dry
4
+ module Monads
5
+ module Do
6
+ # Do::All automatically wraps methods defined in a class with an unwrapping block.
7
+ # Similar to what `Do.for(...)` does except wraps every method so you don't have
8
+ # to list them explicitly.
9
+ #
10
+ # @example annotated example
11
+ #
12
+ # require 'dry/monads/do/all'
13
+ # require 'dry/monads/result'
14
+ #
15
+ # class CreateUser
16
+ # include Dry::Monads::Do::All
17
+ # include Dry::Monads::Result::Mixin
18
+ #
19
+ # def call(params)
20
+ # # Unwrap a monadic value using an implicitly passed block
21
+ # # if `validates` returns Failure, the execution will be halted
22
+ # values = yield validate(params)
23
+ # user = create_user(values)
24
+ # # If another block is passed to a method then takes
25
+ # # precedence over the unwrapping block
26
+ # safely_subscribe(values[:email]) { Logger.info("Already subscribed") }
27
+ #
28
+ # Success(user)
29
+ # end
30
+ #
31
+ # def validate(params)
32
+ # if params.key?(:email)
33
+ # Success(email: params[:email])
34
+ # else
35
+ # Failure(:no_email)
36
+ # end
37
+ # end
38
+ #
39
+ # def create_user(user)
40
+ # # Here a block is passed to the method but we don't use it
41
+ # UserRepo.new.add(user)
42
+ # end
43
+ #
44
+ # def safely_subscribe(email)
45
+ # repo = SubscriptionRepo.new
46
+ #
47
+ # if repo.subscribed?(email)
48
+ # # This calls the logger because a block
49
+ # # explicitly passed from `call`
50
+ # yield
51
+ # else
52
+ # repo.subscribe(email)
53
+ # end
54
+ # end
55
+ # end
56
+ #
57
+ module All
58
+ # @private
59
+ class MethodTracker < Module
60
+ attr_reader :wrappers
61
+
62
+ def initialize(wrappers)
63
+ super()
64
+
65
+ @wrappers = wrappers
66
+ tracker = self
67
+
68
+ module_eval do
69
+ define_method(:method_added) do |method|
70
+ super(method)
71
+
72
+ tracker.wrap_method(method)
73
+ end
74
+ end
75
+ end
76
+
77
+ def extend_object(target)
78
+ super
79
+ target.prepend(wrappers)
80
+ end
81
+
82
+ def wrap_method(method)
83
+ Do.wrap_method(wrappers, method)
84
+ end
85
+ end
86
+
87
+ # @private
88
+ def self.included(base)
89
+ super
90
+
91
+ tracker = MethodTracker.new(Module.new)
92
+ base.extend(tracker)
93
+ base.instance_methods(false).each { |m| tracker.wrap_method(m) }
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -60,6 +60,9 @@ module Dry
60
60
  # @see Dry::Monads::Lazy
61
61
  Lazy = Lazy
62
62
 
63
+ # @see Dry::Monads::Unit
64
+ Unit = Unit
65
+
63
66
  # Lazy constructors
64
67
  module Constructors
65
68
  # Lazy computation contructor
@@ -74,5 +77,7 @@ module Dry
74
77
  include Constructors
75
78
  end
76
79
  end
80
+
81
+ extend Lazy::Mixin::Constructors
77
82
  end
78
83
  end
@@ -1,5 +1,4 @@
1
1
  require 'dry/equalizer'
2
- require 'dry/core/deprecations'
3
2
 
4
3
  require 'dry/monads/maybe'
5
4
  require 'dry/monads/task'
@@ -232,7 +231,7 @@ module Dry
232
231
  coerce(value.drop(1))
233
232
  end
234
233
 
235
- # Turns the list into a types one.
234
+ # Turns the list into a typed one.
236
235
  # Type is required for some operations like .traverse.
237
236
  #
238
237
  # @param type [Monad] Monad instance
@@ -216,5 +216,78 @@ module Dry
216
216
  include Constructors
217
217
  end
218
218
  end
219
+
220
+ extend Maybe::Mixin::Constructors
221
+
222
+ # @see Maybe::Some
223
+ Some = Maybe::Some
224
+ # @see Maybe::None
225
+ None = Maybe::None
226
+
227
+ class Result
228
+ class Success < Result
229
+ # @return [Maybe::Some]
230
+ def to_maybe
231
+ Kernel.warn 'Success(nil) transformed to None' if @value.nil?
232
+ Dry::Monads::Maybe(@value)
233
+ end
234
+ end
235
+
236
+ class Failure < Result
237
+ # @return [Maybe::None]
238
+ def to_maybe
239
+ Maybe::None.new(trace)
240
+ end
241
+ end
242
+ end
243
+
244
+ class Task
245
+ # Converts to Maybe. Blocks the current thread if required.
246
+ #
247
+ # @return [Maybe]
248
+ def to_maybe
249
+ if promise.wait.fulfilled?
250
+ Maybe::Some.new(promise.value)
251
+ else
252
+ Maybe::None.new(RightBiased::Left.trace_caller)
253
+ end
254
+ end
255
+ end
256
+
257
+ class Try
258
+ class Value < Try
259
+ # @return [Maybe]
260
+ def to_maybe
261
+ Dry::Monads::Maybe(@value)
262
+ end
263
+ end
264
+
265
+ class Error < Try
266
+ # @return [Maybe::None]
267
+ def to_maybe
268
+ Maybe::None.new(RightBiased::Left.trace_caller)
269
+ end
270
+ end
271
+ end
272
+
273
+ class Validated
274
+ class Valid < Validated
275
+ # Converts to Maybe::Some
276
+ #
277
+ # @return [Maybe::Some]
278
+ def to_maybe
279
+ Maybe.pure(value!)
280
+ end
281
+ end
282
+
283
+ class Invalid < Validated
284
+ # Converts to Maybe::None
285
+ #
286
+ # @return [Maybe::None]
287
+ def to_maybe
288
+ Maybe::None.new(RightBiased::Left.trace_caller)
289
+ end
290
+ end
291
+ end
219
292
  end
220
293
  end
@@ -1,9 +1,9 @@
1
1
  require 'dry/equalizer'
2
- require 'dry/core/constants'
3
2
 
3
+ require 'dry/monads/undefined'
4
4
  require 'dry/monads/right_biased'
5
5
  require 'dry/monads/transformer'
6
- require 'dry/monads/maybe'
6
+ require 'dry/monads/conversion_stubs'
7
7
 
8
8
  module Dry
9
9
  module Monads
@@ -12,6 +12,7 @@ module Dry
12
12
  # @api public
13
13
  class Result
14
14
  include Transformer
15
+ include ConversionStubs[:to_maybe, :to_validated]
15
16
 
16
17
  # @return [Object] Successful result
17
18
  attr_reader :success
@@ -107,25 +108,12 @@ module Dry
107
108
  end
108
109
  alias_method :inspect, :to_s
109
110
 
110
- # @return [Maybe::Some]
111
- def to_maybe
112
- Kernel.warn 'Success(nil) transformed to None' if @value.nil?
113
- Dry::Monads::Maybe(@value)
114
- end
115
-
116
111
  # Transforms to a Failure instance
117
112
  #
118
113
  # @return [Result::Failure]
119
114
  def flip
120
115
  Failure.new(@value, RightBiased::Left.trace_caller)
121
116
  end
122
-
123
- # Transforms to Validated
124
- #
125
- # @return [Validated::Valid]
126
- def to_validated
127
- Validated::Valid.new(value!)
128
- end
129
117
  end
130
118
 
131
119
  # Represents a value of a failed operation.
@@ -206,11 +194,6 @@ module Dry
206
194
  end
207
195
  alias_method :inspect, :to_s
208
196
 
209
- # @return [Maybe::None]
210
- def to_maybe
211
- Maybe::None.new(trace)
212
- end
213
-
214
197
  # Transform to a Success instance
215
198
  #
216
199
  # @return [Result::Success]
@@ -232,13 +215,6 @@ module Dry
232
215
  def ===(other)
233
216
  Failure === other && failure === other.failure
234
217
  end
235
-
236
- # Transforms to Validated
237
- #
238
- # @return [Validated::Valid]
239
- def to_validated
240
- Validated::Invalid.new(failure, trace)
241
- end
242
218
  end
243
219
 
244
220
  # A module that can be included for easier access to Result monads.
@@ -290,5 +266,96 @@ module Dry
290
266
  include Constructors
291
267
  end
292
268
  end
269
+
270
+ extend Result::Mixin::Constructors
271
+
272
+ # @see Result::Success
273
+ Success = Result::Success
274
+ # @see Result::Failure
275
+ Failure = Result::Failure
276
+
277
+ # Creates a module that has two methods: `Success` and `Failure`.
278
+ # `Success` is identical to {Result::Mixin::Constructors#Success} and Failure
279
+ # rejects values that don't conform the value of the `error`
280
+ # parameter. This is essentially a Result type with the `Failure` part
281
+ # fixed.
282
+ #
283
+ # @example using dry-types
284
+ # module Types
285
+ # include Dry::Types.module
286
+ # end
287
+ #
288
+ # class Operation
289
+ # # :user_not_found and :account_not_found are the only
290
+ # # values allowed as failure results
291
+ # Error =
292
+ # Types.Value(:user_not_found) |
293
+ # Types.Value(:account_not_found)
294
+ #
295
+ # def find_account(id)
296
+ # account = acount_repo.find(id)
297
+ #
298
+ # account ? Success(account) : Failure(:account_not_found)
299
+ # end
300
+ #
301
+ # def find_user(id)
302
+ # # ...
303
+ # end
304
+ # end
305
+ #
306
+ # @param error [#===] the type of allowed failures
307
+ # @return [Module]
308
+ def self.Result(error, **options)
309
+ Result::Fixed[error, **options]
310
+ end
311
+
312
+ class Task
313
+ # Converts to Result. Blocks the current thread if required.
314
+ #
315
+ # @return [Result]
316
+ def to_result
317
+ if promise.wait.fulfilled?
318
+ Result::Success.new(promise.value)
319
+ else
320
+ Result::Failure.new(promise.reason, RightBiased::Left.trace_caller)
321
+ end
322
+ end
323
+ end
324
+
325
+ class Try
326
+ class Value < Try
327
+ # @return [Result::Success]
328
+ def to_result
329
+ Dry::Monads::Result::Success.new(@value)
330
+ end
331
+ end
332
+
333
+ class Error < Try
334
+ # @return [Result::Failure]
335
+ def to_result
336
+ Result::Failure.new(exception, RightBiased::Left.trace_caller)
337
+ end
338
+ end
339
+ end
340
+
341
+ class Validated
342
+ class Valid < Validated
343
+ # Converts to Result::Success
344
+ #
345
+ # @return [Result::Success]
346
+ def to_result
347
+ Result.pure(value!)
348
+ end
349
+ end
350
+
351
+ class Invalid < Validated
352
+ # Concerts to Result::Failure
353
+ #
354
+ # @return [Result::Failure]
355
+ def to_result
356
+ Result::Failure.new(error, RightBiased::Left.trace_caller)
357
+ end
358
+ end
359
+ end
293
360
  end
294
361
  end
@@ -1,5 +1,6 @@
1
1
  require 'dry/core/constants'
2
2
 
3
+ require 'dry/monads/unit'
3
4
  require 'dry/monads/curry'
4
5
  require 'dry/monads/errors'
5
6
 
@@ -99,13 +100,13 @@ module Dry
99
100
  # otherwise returns the argument.
100
101
  #
101
102
  # @example happy path
102
- # create_user = Dry::Monads::Right(CreateUser.new)
103
- # name = Right("John")
103
+ # create_user = Dry::Monads::Success(CreateUser.new)
104
+ # name = Success("John")
104
105
  # create_user.apply(name) # equivalent to CreateUser.new.call("John")
105
106
  #
106
107
  # @example unhappy path
107
- # name = Left(:name_missing)
108
- # create_user.apply(name) # => Left(:name_missing)
108
+ # name = Failure(:name_missing)
109
+ # create_user.apply(name) # => Failure(:name_missing)
109
110
  #
110
111
  # @return [RightBiased::Left,RightBiased::Right]
111
112
  def apply(val = Undefined)
@@ -116,12 +117,24 @@ module Dry
116
117
  Undefined.default(val) { yield }.fmap { |unwrapped| curry.(unwrapped) }
117
118
  end
118
119
 
119
- # @param other [RightBiased]
120
+ # @param other [Object]
120
121
  # @return [Boolean]
121
122
  def ===(other)
122
123
  self.class == other.class && value! === other.value!
123
124
  end
124
125
 
126
+ # Maps the value to Dry::Monads::Unit, useful when you don't care
127
+ # about the actual value.
128
+ #
129
+ # @example
130
+ # Dry::Monads::Success(:success).discard
131
+ # # => Success(Unit)
132
+ #
133
+ # @return [RightBiased::Right]
134
+ def discard
135
+ fmap { Unit }
136
+ end
137
+
125
138
  private
126
139
 
127
140
  # @api private
@@ -215,6 +228,14 @@ module Dry
215
228
  def apply(*)
216
229
  self
217
230
  end
231
+
232
+ # Returns self back. It exists to keep the interface
233
+ # identical to that of {RightBiased::Right}.
234
+ #
235
+ # @return [RightBiased::Left]
236
+ def discard
237
+ fmap { Unit }
238
+ end
218
239
  end
219
240
  end
220
241
  end
@@ -1,6 +1,8 @@
1
1
  require 'concurrent/promise'
2
2
 
3
+ require 'dry/monads/unit'
3
4
  require 'dry/monads/curry'
5
+ require 'dry/monads/conversion_stubs'
4
6
 
5
7
  module Dry
6
8
  module Monads
@@ -68,6 +70,8 @@ module Dry
68
70
  end
69
71
  end
70
72
 
73
+ include ConversionStubs[:to_maybe, :to_result]
74
+
71
75
  # @api private
72
76
  attr_reader :promise
73
77
  protected :promise
@@ -112,28 +116,6 @@ module Dry
112
116
  end
113
117
  alias_method :then, :bind
114
118
 
115
- # Converts to Result. Blocks the current thread if required.
116
- #
117
- # @return [Result]
118
- def to_result
119
- if promise.wait.fulfilled?
120
- Result::Success.new(promise.value)
121
- else
122
- Result::Failure.new(promise.reason, RightBiased::Left.trace_caller)
123
- end
124
- end
125
-
126
- # Converts to Maybe. Blocks the current thread if required.
127
- #
128
- # @return [Maybe]
129
- def to_maybe
130
- if promise.wait.fulfilled?
131
- Maybe::Some.new(promise.value)
132
- else
133
- Maybe::None.new(RightBiased::Left.trace_caller)
134
- end
135
- end
136
-
137
119
  # @return [String]
138
120
  def to_s
139
121
  state = case promise.state
@@ -244,6 +226,13 @@ module Dry
244
226
  bind { |f| arg.fmap { |v| curry(f).(v) } }
245
227
  end
246
228
 
229
+ # Maps a successful result to Unit, effectively discards it
230
+ #
231
+ # @return [Task]
232
+ def discard
233
+ fmap { Unit }
234
+ end
235
+
247
236
  private
248
237
 
249
238
  # @api private
@@ -271,7 +260,11 @@ module Dry
271
260
  #
272
261
  # @api public
273
262
  module Mixin
274
- Task = Task # @private
263
+ # @private
264
+ Task = Task
265
+
266
+ # @see Dry::Monads::Unit
267
+ Unit = Unit # @private
275
268
 
276
269
  # Created a mixin with the given executor injected.
277
270
  #
@@ -305,5 +298,7 @@ module Dry
305
298
  include Constructors
306
299
  end
307
300
  end
301
+
302
+ extend Task::Mixin::Constructors
308
303
  end
309
304
  end
@@ -2,8 +2,7 @@ require 'dry/equalizer'
2
2
  require 'dry/core/deprecations'
3
3
 
4
4
  require 'dry/monads/right_biased'
5
- require 'dry/monads/result'
6
- require 'dry/monads/maybe'
5
+ require 'dry/monads/conversion_stubs'
7
6
 
8
7
  module Dry
9
8
  module Monads
@@ -15,6 +14,8 @@ module Dry
15
14
  # @private
16
15
  DEFAULT_EXCEPTIONS = [StandardError].freeze
17
16
 
17
+ include ConversionStubs[:to_maybe, :to_result]
18
+
18
19
  # @return [Exception] Caught exception
19
20
  attr_reader :exception
20
21
 
@@ -153,16 +154,6 @@ module Dry
153
154
  Error.new(e)
154
155
  end
155
156
 
156
- # @return [Maybe]
157
- def to_maybe
158
- Dry::Monads::Maybe(@value)
159
- end
160
-
161
- # @return [Result::Success]
162
- def to_result
163
- Dry::Monads::Result::Success.new(@value)
164
- end
165
-
166
157
  # @return [String]
167
158
  def to_s
168
159
  "Try::Value(#{ @value.inspect })"
@@ -182,16 +173,6 @@ module Dry
182
173
  @exception = exception
183
174
  end
184
175
 
185
- # @return [Maybe::None]
186
- def to_maybe
187
- Maybe::None.new(RightBiased::Left.trace_caller)
188
- end
189
-
190
- # @return [Result::Failure]
191
- def to_result
192
- Result::Failure.new(exception, RightBiased::Left.trace_caller)
193
- end
194
-
195
176
  # @return [String]
196
177
  def to_s
197
178
  "Try::Error(#{ exception.class }: #{ exception.message })"
@@ -291,5 +272,7 @@ module Dry
291
272
  end
292
273
  end
293
274
  end
275
+
276
+ extend Try::Mixin::Constructors
294
277
  end
295
278
  end
@@ -0,0 +1,8 @@
1
+ require 'dry/core/constants'
2
+
3
+ module Dry
4
+ module Monads
5
+ # @private
6
+ Undefined = Dry::Core::Constants::Undefined
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Monads
5
+ Unit = Object.new.tap do |unit|
6
+ def unit.to_s
7
+ 'Unit'
8
+ end
9
+
10
+ def unit.inspect
11
+ 'Unit'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,6 @@
1
- require 'dry/monads/maybe'
2
- require 'dry/monads/result'
1
+ require 'dry/monads/conversion_stubs'
2
+ require 'dry/monads/undefined'
3
+ require 'dry/monads/right_biased'
3
4
 
4
5
  module Dry
5
6
  module Monads
@@ -19,6 +20,8 @@ module Dry
19
20
  # # => Valid(List['London', 'John'])
20
21
  #
21
22
  class Validated
23
+ include ConversionStubs[:to_maybe, :to_result]
24
+
22
25
  class << self
23
26
  # Wraps a value with `Valid`.
24
27
  #
@@ -121,18 +124,10 @@ module Dry
121
124
  end
122
125
  alias_method :to_s, :inspect
123
126
 
124
- # Converts to Maybe::Some
125
- #
126
- # @return [Maybe::Some]
127
- def to_maybe
128
- Maybe.pure(value!)
129
- end
130
-
131
- # Converts to Result::Success
132
- #
133
- # @return [Result::Success]
134
- def to_result
135
- Result.pure(value!)
127
+ # @param other [Object]
128
+ # @return [Boolean]
129
+ def ===(other)
130
+ self.class == other.class && value! === other.value!
136
131
  end
137
132
  end
138
133
 
@@ -213,18 +208,10 @@ module Dry
213
208
  end
214
209
  alias_method :to_s, :inspect
215
210
 
216
- # Converts to Maybe::None
217
- #
218
- # @return [Maybe::None]
219
- def to_maybe
220
- Maybe::None.new(RightBiased::Left.trace_caller)
221
- end
222
-
223
- # Concerts to Result::Failure
224
- #
225
- # @return [Result::Failure]
226
- def to_result
227
- Result::Failure.new(error, RightBiased::Left.trace_caller)
211
+ # @param other [Object]
212
+ # @return [Boolean]
213
+ def ===(other)
214
+ self.class == other.class && error === other.error
228
215
  end
229
216
  end
230
217
 
@@ -279,5 +266,32 @@ module Dry
279
266
  include Constructors
280
267
  end
281
268
  end
269
+
270
+ extend Validated::Mixin::Constructors
271
+
272
+ # @see Validated::Valid
273
+ Valid = Validated::Valid
274
+ # @see Validated::Invalid
275
+ Invalid = Validated::Invalid
276
+
277
+ class Result
278
+ class Success < Result
279
+ # Transforms to Validated
280
+ #
281
+ # @return [Validated::Valid]
282
+ def to_validated
283
+ Validated::Valid.new(value!)
284
+ end
285
+ end
286
+
287
+ class Failure < Result
288
+ # Transforms to Validated
289
+ #
290
+ # @return [Validated::Valid]
291
+ def to_validated
292
+ Validated::Invalid.new(failure, trace)
293
+ end
294
+ end
295
+ end
282
296
  end
283
297
  end
@@ -1,6 +1,6 @@
1
1
  module Dry
2
2
  module Monads
3
3
  # @private
4
- VERSION = '1.0.0.beta1'.freeze
4
+ VERSION = '1.0.0.beta2'.freeze
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-monads
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta1
4
+ version: 1.0.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikita Shilnikov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-10 00:00:00.000000000 Z
11
+ date: 2018-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-equalizer
@@ -137,8 +137,11 @@ files:
137
137
  - dry-monads.gemspec
138
138
  - lib/dry-monads.rb
139
139
  - lib/dry/monads.rb
140
+ - lib/dry/monads/all.rb
141
+ - lib/dry/monads/conversion_stubs.rb
140
142
  - lib/dry/monads/curry.rb
141
143
  - lib/dry/monads/do.rb
144
+ - lib/dry/monads/do/all.rb
142
145
  - lib/dry/monads/either.rb
143
146
  - lib/dry/monads/errors.rb
144
147
  - lib/dry/monads/lazy.rb
@@ -151,6 +154,8 @@ files:
151
154
  - lib/dry/monads/transformer.rb
152
155
  - lib/dry/monads/traverse.rb
153
156
  - lib/dry/monads/try.rb
157
+ - lib/dry/monads/undefined.rb
158
+ - lib/dry/monads/unit.rb
154
159
  - lib/dry/monads/validated.rb
155
160
  - lib/dry/monads/version.rb
156
161
  - lib/json/add/dry/monads/maybe.rb