These are chat archives for dry-rb/chat

30th
Jul 2018
Son Dang
@sondnm
Jul 30 2018 01:18
@jonahx you can use rodauth by the same author of sequel and roda http://rodauth.jeremyevans.net
Jonah
@jonahx
Jul 30 2018 01:26
@sondnm looks very nice, ty
Artem Pyankov
@iBublik
Jul 30 2018 08:26
Hi everyone! I'm trying to implement custom type that checks object's able to respond to method call. Currently I've implemented it this way: Callable = Any.constructor do |value| if value.respond_to?(:call) value else raise("#{value.inspect} does not respond to `call`") end end
I want to replace it with something like Any.constrained(???). Any ideas how to do that?
oops, sorry for ugly code formatting, I'm new to gitter :)
Tim Riley
@timriley
Jul 30 2018 08:33
I haven’t done anything like this before, @iBublik, but I should mention that dry-types is mostly about helping you with data types, and an object responding to #call isn’t really “data” IMO
In cases like that I wouldn’t bother checking
You’ll get an error when you try #call-ing the object, anyway
Jonah
@jonahx
Jul 30 2018 12:15
Two questions about dry-monads:
  1. It’s unclear to me why we need both Result and Try. They feel like they’re solving the same problem. Can someone clarify what the difference is, why we need both, and which situations can be solved by one but not the other?
  2. Why do we unwrap a Value object with value! rather than value? Is there any mutation happening?
Jonah
@jonahx
Jul 30 2018 12:22

Separately, I can’t seem to get it working. This is on my machine after installing gem install dry-monads:

require 'dry-monads'

M = Dry::Monads

M::List[1, 2].bind { |x| [x + 1] }
# `<main>': uninitialized constant Dry::Monads::List (NameError)

cc @flash-gordon

Artem Pyankov
@iBublik
Jul 30 2018 15:20
@timriley thanks for your answer. I'm using dry-struct as a replacement for Virtus. So my objects aren't pure data, it holds some behaviour. For example, I have an object that accepts collection and callable object for grouping that data, so I can use it like this: collection.group_by(&grouper). While typing this, I understood that I should check method to_proc, not call :D Or maybe you're right and it's useless... I'll think about
Nikita Shilnikov
@flash-gordon
Jul 30 2018 15:49
@jonahx should be require 'dry/monads/all'
Try and Result have different monads, Try is for handling exceptions whereas Result is a simple ADT
with two constructors
which you call directly for separate Success/Failure paths
Jonah
@jonahx
Jul 30 2018 15:51
@flash-gordon thanks. i still don’t see why those are fundamentally different tho? meaning couldn’t you use Success for a successful try and a Failure for the exception?
Nikita Shilnikov
@flash-gordon
Jul 30 2018 15:53
You mean Try returning Success/Failure?
like, Try[StandardError] { ... } # => Success/Failure?
this won't work because you can't fmap/flat_map it further
so it won't be a monad
i.e. Try[StandardError] { possibly throws error }.fmap { possibly throws error }
^this won't work with Success/Failure
Jonah
@jonahx
Jul 30 2018 16:08

@flash-gordon my mental model must be wrong here somehow… but the way i’m thinking about the Result monad. You are chaining together a bunch of things that can succeed or fail, and you want the result of the whole chain. from the docs:

Success(i).bind do |value|
      if value > 1
        Success(value + 3)
      else
        Failure("value was less than 1")
      end
    end.bind do |value|
      if value % 2 == 0
        Success(value * 2)
      else
        Failure("value was not even")
      end
    end

my thinking is that, eg, if value > 1 could replaced by something that possibly raises an error, you could rescue that error and return a Failure. that’s seems analogous to me to the Try case. where is my thinking wrong?

Nikita Shilnikov
@flash-gordon
Jul 30 2018 16:09
ah, you're right, but you need to catch errors within a do/end block
and Try rescues for you
I prefer Result but I do use Try in some cases
but I convert the result to ... Result in the end
Jonah
@jonahx
Jul 30 2018 16:12
@flash-gordon ok, i see that, but to use your example from above why not: Try[StandardError] { possibly throws error }.bind |result| { possibly throws error } and then the whole thing is still handled by the Result construct? am i still missing some subtle difference?
Nikita Shilnikov
@flash-gordon
Jul 30 2018 16:12
Try[*HttpErrors] { ... some http calls ... }.to_result <- something like this
@jonahx better call .to_result I guess, it also makes sense
once you exit the "exceptional" part of the code
Jonah
@jonahx
Jul 30 2018 16:15
@flash-gordon and what’s the reason for the ! at the end of value!?
Nikita Shilnikov
@flash-gordon
Jul 30 2018 16:16
it can raise an error so ! here stands for "danger"
if you call value! on Failure it throws an exception
we used to have value previously
which returned nil, this was a bad idea orignated from the kleisli gem
Jonah
@jonahx
Jul 30 2018 16:18
makes sense. you will avoid this if you just turn it into a result first? the if checks like res.value! if res.value? feel like they defeat the purpose of the monad to me...
Nikita Shilnikov
@flash-gordon
Jul 30 2018 16:20
value! behaviour is the same for Try and Result. Simply, there is no value for failure cases
they have different accessors for errors
I call value! very occasionally
you don't need it actually
there is dry-matcher for handling results
also, there's do notation from dry-monads, this is enough
fmap/bind/value_or/or for everything else
Jonah
@jonahx
Jul 30 2018 16:23
ok i will do a bit more reading of the docs and look at dry-matcher, and post again tonight if i am still unclear. thx for the answers!
Sam Starling
@samstarling
Jul 30 2018 17:02
Hey – I'm reading this page and trying to work out how to make an attribute mandatory, but only if another one is specified and valid. For example, if amount is above 1000, then I'd like a reference field to be included... How can I do that with dry-validation?
Jaromír Červenka
@Cervajz
Jul 30 2018 17:26
@samstarling Hey, how about this:
ExampleSchema = Dry::Validation.Schema do
  required(:amount).filled(:int?)
  optional(:reference).maybe(:int?)

  rule(reference: %i[reference amount]) do |reference, amount|
    amount.gt?(1000).then(reference.filled?)
  end
end
=>
[27] pry(main)> reload!; ExampleSchema.call({amount: 100}).errors
Reloading...
=> {}
[28] pry(main)> reload!; ExampleSchema.call({amount: 1001}).errors
Reloading...
=> {:reference=>["must be filled"]}
[29] pry(main)> reload!; ExampleSchema.call({amount: 1001, reference: 1}).errors
Reloading...
=> {}
[30] pry(main)>
Jaromír Červenka
@Cervajz
Jul 30 2018 17:31
@samstarling With amount being optional:
ExampleSchema = Dry::Validation.Schema do
  optional(:amount).filled(:int?)
  optional(:reference).maybe(:int?)

  rule(reference: %i[reference amount]) do |reference, amount|
    amount.filled? > amount.gt?(1000).then(reference.filled?)
  end
end

...

[40] pry(main)> reload!; ExampleSchema.call({}).errors
Reloading...
=> {}
[41] pry(main)> reload!; ExampleSchema.call({amount: 100}).errors
Reloading...
=> {}
[42] pry(main)> reload!; ExampleSchema.call({amount: 10001}).errors
Reloading...
=> {:reference=>["must be filled"]}
[43] pry(main)>
Grant Shangreaux
@gcentauri
Jul 30 2018 18:17
this is the same pattern i've used as well