dry-rb / dry-monads Goto Github PK
View Code? Open in Web Editor NEWUseful, common monads in idiomatic Ruby
Home Page: https://dry-rb.org/gems/dry-monads
License: MIT License
Useful, common monads in idiomatic Ruby
Home Page: https://dry-rb.org/gems/dry-monads
License: MIT License
The Either monads doubles as Result in dry-rb. However I found the way .value
works to be unexpected.
Consider this:
result = format_time("Invalid input")
"The time is #{result.value}"
This snippet would produce a wrong output e.g. "The time is Wrong Input".
Optimally I would do .success?
before doing this. But ruby is not haskell and pattern matching is not required. So is easy to be lazy and code the happy path.
So maybe consider adding a Result
monad that:
result.value
will raise error
.error
e.g. result.error
== "Wrong Input"
With something like this my above snippet will raise an error instead of giving me a wrong output, which I think is preferable.
Pattern matching doesn't work for Success([1]) in Success([a])
, hoverer it works for Success[1] in Success[a]
[1] pry(main)> Success([1]) in Success([a])
NoMatchingPatternError: Success([1])
from (pry):41:in `<main>'
# The working examples:
[2] pry(main)> Success[1] in Success[a]
=> nil
[3] pry(main)> Success([1]) in Success[a]
=> nil
Success([a])
should work exactly the same as Success[a]
, otherwise examples should be updated.
Success[1]
)I have classes:
require 'dry/monads'
require 'dry/monads/do'
class BaseService
include Dry::Monads[:result, :do]
end
class Create < BaseService
include Rails.application.routes.url_helpers
def call
path = post_url
end
end
When I call post_url
then I have error:
post_url StackError: stack level too deep
/Users/mateuszbialowas/.asdf/installs/ruby/3.1.0/lib/ruby/gems/3.1.0/gems/dry-monads-1.5.0/lib/dry/monads/do.rb:128:in `default_url_options'
When I remove the inheritance from BaseService, then I can call post_url with no error.
How can I use dry monads with do notation and call URL helpers?
I would like to call post_url with no error. I want to be returned full path: http://localhost:3000/posts
I recently added the following to encapsulate pagination and I think it could be generally useful. Please consider implementing something similar.
require 'dry/monads/list'
require 'dry/monads/maybe'
require 'dry/monads/result'
module Dry
module Monads
class List
def self.unfold(cursor)
list = []
loop do
result = yield cursor
case result
when Monads::Success
maybe = result.value!
case maybe
when Monads::Some
cursor = maybe.value!.first
list << maybe.value!.last
when Monads::None
break
end
when Monads::Failure
return result
end
end
Monads::Success(new(list))
end
end
end
end
Coming from Haskell and Scala, the naming of List#traverse
threw me off.
The Haskell signatures of both functions for reference:
sequence :: (Traversable t, Monad m) => t (m a) -> m (t a)
traverse :: (Traverseable t, Applicative f) => (a -> f b) -> t a -> f (t b)
Given List::Validated
that contains items of both Valid
and Invalid
types:
list = M::List::Validated[M.Valid(1), M.Invalid("error1"), M.Valid(2), M.Invalid("error2")]
Expected behavior:
list.traverse
=> Invalid(List<Validated>["error1", "error2"])
Actual behavior:
list.traverse
=> Invalid(List<Validated>["error2"])
When a right-biased type is wrapping an array value, it's not possible to unwrap it via pattern-matching.
require "dry/monads"
include Dry::Monads[:result]
Success(1) in Success(x) # => true
x # => 1 AS EXPECTED
Success([1]) in Success(x) # => true
x # => 1 IT SHOULD BE [1]
Success([1, 2]) in Success(x) # => true
x # => 1 IT SHOULD BE [1, 2]
The wrapped value should be extracted as it is.
We should stop branching depending on whether the wrapped value is an array. Instead, #deconstruct
should be simply:
def deconstruct
if Unit.equal?(@value)
EMPTY_ARRAY
else
[@value]
end
end
However, the above would be a breaking change. Now we have:
Success([1]) in [1] # => true
The above would change:
Success([1]) in [1] # => false
IMO, we should nonetheless change it, as the most common use-case for results is extracting the wrapped value after some operations have been performed.
I'm happy to submit a PR fixing the issue. If we consider it as a bug fix, it should go into the next minor or patch release. However, if we consider it as a new feature breaking code, we should wait until v2.0.
Unlike #42 this not supposed to be a major change, but as noted there the Either
monad does somewhat double as a Result
monad. So given that right?
and left?
are already aliased to success?
and failure?
, would it be reasonable to also alias the "constructors" defined in the mixin?
Essentially just this:
index 083c4f0..f48b32e 100644
--- a/lib/dry/monads/either.rb
+++ b/lib/dry/monads/either.rb
@@ -210,2 +210,3 @@ module Dry
end
+ alias Success Right
@@ -216,2 +217,3 @@ module Dry
end
+ alias Failure Left
end
For reference, Rust originally had both, but then went with Result for the standard library. Relevant quote:
[W]e actually used to have both Either and Result, until one day we went through and realized that no code in existence was using Either and decided to go all-in on Result instead.
On a side-note, I find it would make dry-transaction
code much more readable to the average user™:
def validate(input)
if input[:email].nil?
Failure(:not_valid)
else
Success(input)
end
end
Using different forms of binding, the destructuring has different way of breaking.
Set up:
class Data < Hash
# This is the data that's passed around. Has method access and different to_s for the sake of testing
def method_missing(name, *args)
return fetch(name) if key?(name) && args.empty?
super
end
def to_s
"<Data #{super}>"
end
end
add_wheel = ->(car, wheel) { car.wheels << wheel; car }
Break 1: wrong number of arguments (1 for 2)
The proc receives a single plain hash {color: 'red', wheels: [], radius: 36}
Right(Data[color: 'red', wheels: []])
.bind(Data[radius: 36], &add_wheel)
Break 2: The second argument is converted to plain hash:
Right(Data[color: 'red', wheels: []])
.bind(add_wheel, Data[radius: 36])
# => <Data {:color=>"red", :wheels=>[{:radius=>36}]}>
It should have been <Data {:color=>"red", :wheels=>[<Data {:radius=>36}>]}>
Break 3 (related to break 2): undefined method 'radius' for {:radius=>36}:Hash
add_big_wheel = ->(car, wheel) { car.wheels << wheel if wheel.radius > 35; car }
Right(Data[color: 'red', wheels: []])
.bind(add_big_wheel, Data[radius: 36])
(The same happens with Result
instead of Right
of course, the examples are with version 0.3.1)
While traversing a Dry::Monads::List
with an empty unfrozen array, returns a Dry::Monads::List
but with a frozen empty array inside. It's an inconsistent behavior as for a Dry::Monads::List
with an array with some content, it always returns a Dry::Monads::List
with unfrozen content.
Array with content example
Dry::Monads::List.new([Success(1), Success(2)]).typed(Dry::Monads::Result).traverse.fmap(&:value)
=> Success([1, 2])
Dry::Monads::List.new([Success(1), Success(2)]).typed(Dry::Monads::Result).traverse.fmap(&:value).value!.frozen?
=> false
Empty array example
Dry::Monads::List.new([]).typed(Dry::Monads::Result).traverse.fmap(&:value)
=> Success([])
Dry::Monads::List.new([]).typed(Dry::Monads::Result).traverse.fmap(&:value).value!.frozen?
=> true
While passing a Dry::Monads::List
with an empty unfrozen array, should return a Dry::Monads::List
with an empty unfrozen array as well
The solution seems pretty simple, it requires to change the dry/monads/list.rb#381
from
EMPTY = List.new([].freeze).freeze
to
EMPTY = List.new([]).freeze
I've submitted a pull request to dry-rb.org, but also want to leave a note in this repo that "Usage" documentation has been moved. Once the pull request has been accepted and published. The README.md of this repo can updated with link to the documentation.
When pattern matching inside a class method an include Dry::Monads[:maybe]
is needed, the extend Dry::Monads[:maybe]
is not enough and causes a crash.
Simple script:
class Foo
include Dry::Monads[:maybe]
extend Dry::Monads[:maybe]
def self.foo
case Some(4)
in Some(a)
puts a
in None()
puts 'none'
end
end
end
Removing any include
or extend
will cause this script to fail:
extend
causes Some(4)
to raise NoMethodError: undefined method
Some' for Foo:Class` (makes sense)extend
causes Some(a)
to raise NameError: uninitialized constant Foo::Some
(?)Ideally I'd like to only have to extend Dry::Monads[:maybe]
for a class method, not both.
Dry::Monads::Task
has an alias for its bind
method as then
.
Ruby 2.6 itself added Kernel#then
as an alias to Kernel#yield_self
(first note) ~11 months after dry-monads
added its alias.
dry-monads
's then
takes precedence, but conflicting with Kernel
methods is something we generally avoid.
Since it's just a helpful alias, and bind
is already used a lot in dry-monads
, I think it could be OK to just remove this alias, though that's technically a breaking change (or could wait for a 2.0 release, since this is pretty low stakes.)
Hi there, I was curious whether you'd be interested in implementing the Map monoid in Dry-Monad, an inspiration for this is https://github.com/DrBoolean/immutable-ext . I was thinking in the line of
name_map = Map(
{
first: Some("Joe"),
last: None(),
}
)
# Hash -> String
SayHi = -> (name_hash) {
name_hash[:first] + name_hash[:last] + ', hi' }
name.bind{|name_hash| Dry::Monads.Maybe(SayHi(name_hash) } # => name_map, because there's a None(); had last name existed, it would be Some("Joe Smith, hi")
Perhaps you could use a hand in implementing this?
Hello everybody. Was playing with dry-monads recently and was very surprised by the fact that there is no .ap method, i know i can implement it using .bind but still it is very convenient method, why it is not in the library by default?
Hey there, thanks for the great work!
Although I've noticed you've been working on dry-effects for algebraic effects (and it might probably be the best solution for my problem), I was wondering what path should I take if I wanted to implement my own monads (logging monad and retry monad would be my top priority for my use case right now).
And btw, is there any roadmap on dry-effects?
Hi there I have rails 5.1.2 with dry-monads 1.1.0 in Gemlock, an upgrade from 0.4.0. I am not getting errors such as
NameError: uninitialized constant Dry::Monads::List
and when calling Dry::Monads.Right(1)
it throws:
NoMethodError: private method
Right' called for Dry::Monads:Module`
What might I be missing?
Hello,
I think it would be nice-to-have some documentation how to use monads in specs/tests properly. It would help in adaptation as well :)
What is going on:
This works:
require 'dry-monads'
M = Dry::Monads
add_two = -> (x) { M.Maybe(x + 2) }
not_add = -> { M.Some(0) }
# This works fine
M.Maybe(nil).or { M.Some(0) }.bind(add_two) # => Some(0)
This does not
require 'dry-monads'
M = Dry::Monads
add_two = -> (x) { M.Maybe(x + 2) }
not_add = -> { M.Some(0) }
M.Maybe(nil).or(not_add).bind(add_two)
resulting in:
Traceback (most recent call last):
monad.rb:8:in `<main>': undefined method `bind' for #<Proc:[email protected]:6 (lambda)> (NoMethodError)
Did you mean? binding
To allow a more monadic handling of errors I'd suggest allowing the existence of the :or method
When you pattern match a Result
that happens to contain an array in a case
statement, the destructured contents of the Result
contains the contents of the array, not the array.
We discovered this because we have a function that decodes JSON and returns the contents in a Result
(Success
if it parsed correctly, Failure
if it didn't). We don't know ahead of time whether or not the Result
will contain an array or not. It's not really a problem to use an if statement in this situation (result.value!
contains the correct value), but it's very surprising that you have to.
result = Success([42])
case result
in Success(value)
value
in Failure(err)
err
end
Returns 42
If instead you set: result = Success([1, 2, 3, 4])
The above case statement will generate a NoMatchingPatternError
.
The case statement should return [42]
(or in the second instance [1, 2, 3, 4]
)
Hi.
After upgrading Dry-Transaction and Dry-Monads we face a big issue with breaking changes.
Currently in each transaction we fail with:
NoMethodError: undefined method Right for.....
As I understand this happen due to replacing Right/Left with Success/Failure, but this change was stated as backward compatible.
However the worst part is that we use method 'value' on Right/Left instance to retrieve transaction results. But currently Success class no longer has method value which was replaced with value!
Can you help to understand do you plan to support backward compatibility and help to rectify such cases and share what are correct usage of DryTransaction to avoid such cases in future?
Honestly these breaking changes force to rework all parts where DryMonads were ever used and cancel almost all benefits of continue using Dry as risk of having similar troubles in future
Our Success(Right) context is:
dry-monads (0.4.0)
dry-core (~> 0.3, >= 0.3.3)
dry-equalizer
dry-transaction (0.13.0)
dry-container (>= 0.2.8)
dry-events (>= 0.1.0)
dry-matcher (>= 0.7.0)
dry-monads (>= 0.4.0)
Our Failure(Left) context is:
dry-monads (1.0.0)
dry-core (~> 0.4, >= 0.4.4)
dry-equalizer
dry-transaction (0.13.0)
dry-container (>= 0.2.8)
dry-events (>= 0.1.0)
dry-matcher (>= 0.7.0)
dry-monads (>= 0.4.0)
Describe the bug
After using dry-transaction
I had a habit to call "commands" special way with passing a block to it and matching result, example:
TransactionCommand.new.call(params) do |result|
result.success { process_success }
result.failure { process_failure }
end
When I tried to do the same with transaction, i encountered bug: yield some_method
always returns nil
.
I know that passing a block to do monad isn't a case, but it'll be nice to raise some error if it's not supported.
To Reproduce
require 'dry/monads'
require 'dry/monads/do'
class ValidateAccount
include Dry::Monads[:result]
include Dry::Monads::Do.for(:call)
def call(params)
values = yield validate(params)
Success(values)
end
def validate(params)
Success(params)
end
end
# always returns `nil`
ValidateAccount.new.call({ hello: 'world' }) do |result|
result.success { |values| puts values }
result.failure { |error| puts error }
end
# returns `Success({ hello: 'world' })`
ValidateAccount.new.call({ hello: 'world' }) do |result|
result.success { |values| puts values }
end
Expected behavior
I'm not sure what behavior should be, but here are possible scenarios:
#call
dry-transaction
-like code: https://dry-rb.org/gems/dry-transaction/0.13/basic-usage/#calling-a-transactionYour environment
In scala it is possible to have Some of null value, which could lead to npe. I see that right now we rise an exception. Maybe we could convert Some(nil) to None automatically?
Now that the proposal
branch has been merged into master
. Could someone with the correct credentials please setup services for Travis CI and CodeClimate. Thanks!
After update to dry-monads
0.3.0 monads convert dry-struct
to hashes:
class A < Dry::Struct
attribute :name, Types::Strict::String
end
m = M.Maybe(A[name: '123'])
# => Some(#<PersistCustomerCommand::A name="123">)
m.bind { |a| a.inspect }
=> "{:name=>\"123\"}"
To remove a difference between M.Maybe and Right/Left monads, and to maintain better polymorphism, M.Maybe
monad should respond to #success?
and #failure?
methods
Currently List#map raises an error if no block is given. However this makes it impossible to do something like map.with_index
as it is possible with all regular ruby Enumerables. I would really like to have this normal behaviour for List#map as well.
Could this be done and are there any cons to it, that I am not aware of?
The following code should work:
case Try { n / d }
in Value(x)
puts "n / d = #{x}"
in Error(ZeroDivisionError => ex)
puts "divisor must not be zero"
else [x]
puts "Got exception: #{x}"
end
But it does not - because Try
implements deconstruct
from RightBiased
, which expects the value to be stored at the @value
instance variable.
Try::Error
does not have a @value
variable - it uses @exception
instead. Thus pattern matching on Error
values never has the expected results.
Current workarounds: call #to_result
.
res = Try { 3 / 2 }
err = Try { 1 / 0 }
# Value works fine
res => Value(x)
puts x # => 1
# Error succeeds on pattern match
err => Try::Error(ex)
# But binds ex to nil, and ex != err.exception
puts ex.inspect => nil
puts err.exception => #<ZeroDivisionError: divided by 0>
# Workaround
err.to_result => Failure(ex)
puts ex.inspect => #<ZeroDivisionError: divided by 0>
Pattern matching on Try::Error
values should bind the exception.
>= 3.0
)Please get it to ~100% before releasing. This is way below our standards :)
It would be very nice if Do.bind
was available without the yield
overload. Please extract https://github.com/dry-rb/dry-monads/blob/master/lib/dry/monads/do.rb#L129-L134. Ideally, there would also be a mixin that would allow for:
bar = bind foo
qux = bind baz(bar)
...
Might seem stupid, but based on all Monads, there is no way for me to execute code on a None or a Left.
My usecase is for example in a service object returning a monad in a controller:
I want to
result.bind do |value|
render json: value.as_json
end.bind_err do |err|
render_error err
end
Unfortunately, can't do it without checking the type.
In addition a simple way to identify if an object is a monad will be extremely helpful: allows interacting with "legacy code" more easily, and wrap the object in a monad if it's not wrapped already.
Hi, thank you for a great Gem!
I have such problem:
class BaseCommand
include Dry::Monads::Result::Mixin
include Dry::Monads::Do.for(:call)
def validate
Success(:ok)
end
end
class CreateCommand < BaseCommand
def call
yield validate
end
end
Expected behaviour:
:ok
Actual behaviour:
LocalJumpError: no block given (yield)
Looks like this is broken because current solution(prepending module that overrides selected methods and makes yield
work without block) works only for parent class and children have their own class in the first place in the method lookup chain
Do you have making it works for children in your plans?
Thanks!
P.S. If I understand the problem correctly then this solution might work
https://stackoverflow.com/questions/49897647/ruby-prepend-module-how-to-prepend-module-to-the-beginning-of-ancestors-array
I can try it a bit later
Thank you for your work on the dry project.
I wanted to make you aware of this incompatibility with spring. Even though I'm opening this ticket on your project first, I intend to open a ticket with spring as well.
Describe the bug
Spring (Rails pre-loader) and dry-monads are incompatible. Spring replaces Kernel#raise with a version that sanitizes backtrace. dry-monads raises an instance of Halt with a frozen EMPTY_ARRAY. An error is generated when Failure(..) is used an a dry-monads Do implementation.
Spring is a installed by default on rails new
To Reproduce
https://github.com/johnmaxwell/spring_dry_monads_break
require 'dry/monads/do'
class BreakHalt
include Dry::Monads[:result]
include Dry::Monads::Do.for(:call)
def call()
yield break_halt
end
def break_halt
Failure('anything')
end
end
BreakHalt.new.call
Results in an error like:
.../lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/application.rb:305:in `reject!': can't modify frozen Array (FrozenError)
Expected behavior
Returns Failure instance
Your environment
Currently, <=>
over Maybe
values returns 0
if both sides are None
, and nil
otherwise. We could lift <=>
over Maybe
.
None <=> Some(2)
=> -1
Some(3) <=> None
=> 1
None <=> None
=> 0
# Calls <=> on the contents of each Some
Some(3) <=> Some(4)
=> -1
M.Right(nil).to_maybe
ArgumentError: nil cannot be some
from /Users/me/.rvm/gems/ruby-2.3.1/gems/dry-monads-0.2.0/lib/dry/monads/maybe.rb:47:in `initialize'
I think Dry::Monads::Maybe(value)
instead Maybe::Some.new(value)
is a solution
Whenever I launch irb
and require dry/monads/maybe
, I cannot call a constructor due to exception
irb(main):001:0> require 'dry/monads/maybe'
=> true
irb(main):002:0> Dry::Monads.Some(2)
Traceback (most recent call last):
16: from /Users/moroz/.rvm/gems/ruby-2.5.3@global/gems/bundler-1.17.2/exe/bundle:22:in `<top (required)>'
15: from /Users/moroz/.rvm/rubies/ruby-2.5.3/lib/ruby/gems/2.5.0/gems/bundler-1.17.2/lib/bundler/friendly_errors.rb:124:in `with_friendly_errors'
14: from /Users/moroz/.rvm/gems/ruby-2.5.3@global/gems/bundler-1.17.2/exe/bundle:30:in `block in <top (required)>'
13: from /Users/moroz/.rvm/rubies/ruby-2.5.3/lib/ruby/gems/2.5.0/gems/bundler-1.17.2/lib/bundler/cli.rb:18:in `start'
12: from /Users/moroz/.rvm/rubies/ruby-2.5.3/lib/ruby/gems/2.5.0/gems/bundler-1.17.2/lib/bundler/vendor/thor/lib/thor/base.rb:466:in `start'
11: from /Users/moroz/.rvm/rubies/ruby-2.5.3/lib/ruby/gems/2.5.0/gems/bundler-1.17.2/lib/bundler/cli.rb:27:in `dispatch'
10: from /Users/moroz/.rvm/rubies/ruby-2.5.3/lib/ruby/gems/2.5.0/gems/bundler-1.17.2/lib/bundler/vendor/thor/lib/thor.rb:387:in `dispatch'
9: from /Users/moroz/.rvm/rubies/ruby-2.5.3/lib/ruby/gems/2.5.0/gems/bundler-1.17.2/lib/bundler/vendor/thor/lib/thor/invocation.rb:126:in `invoke_command'
8: from /Users/moroz/.rvm/rubies/ruby-2.5.3/lib/ruby/gems/2.5.0/gems/bundler-1.17.2/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
7: from /Users/moroz/.rvm/rubies/ruby-2.5.3/lib/ruby/gems/2.5.0/gems/bundler-1.17.2/lib/bundler/cli.rb:463:in `exec'
6: from /Users/moroz/.rvm/rubies/ruby-2.5.3/lib/ruby/gems/2.5.0/gems/bundler-1.17.2/lib/bundler/cli/exec.rb:28:in `run'
5: from /Users/moroz/.rvm/rubies/ruby-2.5.3/lib/ruby/gems/2.5.0/gems/bundler-1.17.2/lib/bundler/cli/exec.rb:74:in `kernel_load'
4: from /Users/moroz/.rvm/rubies/ruby-2.5.3/lib/ruby/gems/2.5.0/gems/bundler-1.17.2/lib/bundler/cli/exec.rb:74:in `load'
3: from /Users/moroz/.rvm/rubies/ruby-2.5.3/bin/irb:11:in `<top (required)>'
2: from (irb):2
1: from /Users/moroz/.rvm/gems/ruby-2.5.3@qlean-app/gems/dry-monads-1.0.1/lib/dry/monads/maybe.rb:212:in `Some'
NameError (uninitialized constant Dry::Monads::Maybe::Mixin::Constructors::Undefined)
Dry::Monads.None
works like a charm.
If nobody fixes it by weekend, I'm going to send a PR
Is there a way I could make the value constructors available globally? A bit like the Kleisli gem
Is there any way to use the do notation when the method is a class method rather than an instance method ?
We are struggling to find an appropriate syntax, and are reluctant to change our existing codebase which does not always instanciate classes ...
Do-notation in method that has a global rescue works incorrectly. Here is an example.
class Documents::CreateDocument
include Dry::Monads::Result::Mixin
include Dry::Monads::Do.for(:call)
def call(document, params)
params = yield validate(params)
document = yield persist(document, params)
document = yield GlobalContainer['document.services.rename_document_command'].call(document)
document = yield GlobalContainer['document.services.create_affiliations'].call(document)
Success(document)
rescue => e
GlobalContainer['support.services.exception_notifier'].call(e)
Failure(e)
end
# ...
end
Lets suggest that validate
is a method that returns Dry::Monads::Failure
for incorrect input.
[1] pry(main)> incorrect_params = {category_id: 0}
=> {:category_id=>0}
[2] pry(main)> result = Documents::CreateDocument.new.call(Document.new, incorrect_params)
=> Failure(#<Dry::Monads::Do::Halt: Dry::Monads::Do::Halt>)
I expected to receive Failure
with result of #validate
method, instead of it I received Failure
with Dry::Monads::Do::Halt
. This happens because https://github.com/dry-rb/dry-monads/blob/master/lib/dry/monads/do.rb#L137 is not called when Halt
is raised here https://github.com/dry-rb/dry-monads/blob/master/lib/dry/monads/do.rb#L132. Halt
is captured by global rescue
is original method.
From my point of view this can be fixed by aliasing and calling original method instead of overriding it.
When using Lazy, and calling wait, I get an error about there being no live threads left.
require 'dry/monads/all'
x = Dry::Monads.Lazy { 1 / 1 }.wait
fatal (No live threads left. Deadlock?)
Lazy(1)
I am wondering if it would be better if or
would return Some
or Right
instead of the result of the given block. Are there any arguments against it, because right now the developer has to make sure, that the result includes conversion to the given type.
However I am aware that this assumes that the block result is always treated as success.
Describe the bug
The Result
monad can only be included in its Fixed form (e.g. Dry::Monads::Result(Error)
) if it has previously been referenced in its 'normal form' (e.g. Dry::Monads[:result]
).
To Reproduce
Attempt to include a Fixed-Result into a class, exactly as seen in the Result documentation, section 'Adding Constraints to Failure values.'
This causes the class to fail construction, with the following error:
NameError (uninitialized constant Dry::Monads::Result::Fixed)
Expected behavior
The Result
monad is included into the class successfully.
Known workaround
If the Result
monad has previously been referenced in its normal form, the fixed form begins working as well. To work around this issue, I require the following file instead of dry/monads/result
where it is needed:
require 'dry/monads/result'
Dry::Monads[:result]
This causes all necessary constants to be instantiated for future use.
Your environment
Describe the bug
Adding include Dry::Monads[:do]
to a class silently removes private
(or indeed any) visibility modifiers from all methods.
To Reproduce
ruby minimal_case.rb
from this gist (sorry, GH wouldn't let me attach the file directly)Expected behaviour
Expected: raises NoMethodError (private method 'reveal' called for #<WithSecret:0xdeadbeef>
Actual: outputs Oops, I swore I wouldn't tell!
to STDOUT
Your environment
Notes
wrap_method
doesn't accept a protection modifier, so all redefined methods become public by default.Do::All
uses Module::instance_methods
to determine what to wrap - but (since at least Ruby 2.0) Module
also provides public_instance_methods
, protected_instance_methods
, and private_instance_methods
. Could these be used to give the wrapper methods the same visibility as the methods they wrap?private
/protected
/public
operate, it might be tricky to do this without accidentally setting the default visibility within the class to a surprising value. I haven't tested this, though.It appears the syntax include Dry::Monads[:do]
doesn't work on class methods.
For example:
class MyTest
class << self
include Dry::Monads[:try, :result, :do]
def foo(x)
a = yield bar(x)
end
def bar(x)
Try { 10 / x }.to_result
end
end
end
irb(main):066:0> MyTest.foo(0)
Traceback (most recent call last):
2: from (irb):66
1: from app/persistence/my_test.rb:45:in `foo'
LocalJumpError (no block given (yield))
The include Dry::Monads[:try, :result, :do]
works as expected if not implemented as class methods.
Also, this syntax works fine on class methods:
class MyTest
class << self
include Dry::Monads[:try, :result]
include Dry::Monads::Do.for(:foo)
def foo(x)
a = yield bar(x)
end
def bar(x)
Try { 10 / x }.to_result
end
end
end
irb(main):059:0> MyTest.foo(0)
=> Failure(#<ZeroDivisionError: divided by 0>)
I looked through the issues briefly. Issue #92 addressed difficulty getting do-notation working with class methods, but only used the Do.for(:foo)
syntax. Also #68 is a different issue, but I noted the syntax to include Dry::Monads::Do::All
; I tried it and ran into the same LocalJumpError
.
My environment
https://dry-rb.org/gems/dry-monads/try/#bind describes fmap
, e.g. mapping with a pure function.
https://dry-rb.org/gems/dry-monads/try/#fmap describes chaining Try
s.
Describe the bug
https://github.com/dry-rb/dry-monads/blob/master/lib/dry/monads/list.rb#L139 states:
Note that this method returns an Array instance, not a List
However, this isn't true; when called with a block, List#map
will return a List.
It also conflicts with the @return [List,Enumerator]
annotation.
I expected the doc comment would be accurate and consistent.
Either the comment needs updating, or the method is working incorrectly.
To Reproduce
require 'dry/monads'
require 'dry/monads/list'
list = Dry::Monads::List['a', 'b', 'c']
mapped = list.map { |char| char.upcase }
pp mapped
Output:
$ ruby list-test.rb
List["A", "B", "C"]
Expected behavior
I expected the output to be an array, like ["A", "B", "C"]
Your environment
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
Ubuntu 14.04.6 LTS
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.