Additional params to .bind method


#1

Hi. I have a question/proposal about dry-monads.

I wrote code like this:

module Website
  class HandleNewOrder
    include Dry::Monads::Either::Mixin

    include AppContainer::Inject[
      'bus.find_or_create_client',
      'bus.create_order'
    ]

    def call(order_params)
      @find_or_create_client.call(extract_client_params(order_params))
        .fmap { |client| build_bus_order_params(order_params, client) }
        .bind(@create_order)
    end
    ...
end

find_or_create_client -returns Right monad with client object inside,
and then i want to ajdust parameters for @create_order without breaking chain (.fmap)

I’m wondering, is it posible to send additional params to the next .bind (which will reduce fmap call from chain ).

Something like this:

def call(order_params)
  @find_or_create_client.call(extract_client_params(order_params)) 
    .bind(@create_order, order_params) # Should call: create_order.call(client, order_params)
end

Or it’s not idiomatic for monads? (I’m not familiar with theory of this concept)


#2

Hello.

That’s a good question. Normally, you’d use currying for this, but Ruby’s support for it is rather cumbersome, have a look:

def call(order_params)
  create_with_params = @create_order.method(:call).curry.(order_params) # you have to change the order of .call arguments to make it work
  @find_or_create_client.call(extract_client_params(order_params))
    .bind(create_with_params)
end

Adding currying arguments as extra args to .bind/.fmap is a nice idea fwiw. I personally use keywords for this and it looks nice to me

def find_or_create_client(param1: param2: , **rest)
  client = # do some stuff using param1 and param2
  Right(
    client: client,
    **rest # just pass anything else "as is"
  )
end

def create_order(order_params: , client: **rest)
  order = # do some stuff with order_params and client
  Right(
    client: client,
    order: order,
    **rest # we probably won't need order_params anymore, so we don't pass it here, but we can if needed
  )
end

def call(order_params)
  find_or_create_client(order_params: order_params).fmap(method(:create_order))
end

I found using keywords more flexible, you can chain you operations more easily with them. But anyway, as I said passing extra arguments to .bind is a good idea and should make no harm (except it will became a tiny bit slower) so I’m going to give it a shot.