Class: DeferrableGratification::Combinators::Bind

Inherits:
DefaultDeferrable show all
Defined in:
lib/deferrable_gratification/combinators/bind.rb

Overview

Combinator that passes the result of one deferred operation to a block that uses that result to begin another deferred operation. The compound operation itself succeeds if the second operation does.

You probably want to call #bind! rather than using this class directly.

If we define Deferrable err (IO a) to be the type of a Deferrable that may perform a side effect and then either succeed with a value of type a or fail with an error of type err, then we expect the block to have type

a -> Deferrable err (IO b)

and if so this combinator (actually Bind.setup!) is a specialisation of the monadic bind operator >>=:

# example: database query that depends on the result of another
Bind.setup!(DB.query('select foo from bar')) do |result|
  DB.query("select baz from quuz where name = '#{result}'")
end

# type signatures, in pseudo-Haskell
(>>=) :: Deferrable err a ->
             (a -> Deferrable err b) -> Deferrable err b
Bind :: Deferrable err a ->
             (a -> Deferrable err (IO b)) -> Deferrable err (IO b)

However, because Ruby doesn’t actually type-check blocks, we can’t enforce that the block really does return a second Deferrable. This therefore also supports (reasonably) arbitrary blocks. However, it’s probably clearer (though equivalent) to use #transform for this case.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(first, options = {}, &block) ⇒ Bind

Prepare to bind block to first, and create the Deferrable that will represent the bind.

Does not actually set up any callbacks or errbacks: call #setup! for that.

Parameters:

  • first (Deferrable)

    operation to bind to.

  • &block

    block to run on success; should return a Deferrable.

Raises:

  • (ArgumentError)

    if called without a block.



46
47
48
49
50
51
52
53
54
55
# File 'lib/deferrable_gratification/combinators/bind.rb', line 46

def initialize(first, options = {}, &block)
  @first = first

  @with_chaining = !options.delete(:without_chaining)
  bad_keys = options.keys.join(', ')
  raise "Unknown options: #{bad_keys}" unless bad_keys.empty?

  raise ArgumentError, 'must pass a block' unless block
  @proc = block
end

Class Method Details

.setup!(first, options = {}, &block) ⇒ Bind

Create a DeferrableGratification::Combinators::Bind and register the callbacks.

Parameters:

  • first (Deferrable)

    operation to bind to.

  • &block

    block to run on success; should return a Deferrable.

Returns:

  • (Bind)

    Deferrable representing the compound operation.

Raises:

  • (ArgumentError)

    if called without a block.



71
72
73
# File 'lib/deferrable_gratification/combinators/bind.rb', line 71

def self.setup!(first, options = {}, &block)
  new(first, options, &block).tap(&:setup!)
end

Instance Method Details

#setup!Object

Register a callback on the first Deferrable to run the bound block on success, and an errback to fail this DeferrableGratification::Combinators::Bind on failure.



59
60
61
62
# File 'lib/deferrable_gratification/combinators/bind.rb', line 59

def setup!
  @first.callback {|*args| run_bound_proc(*args) }
  @first.errback {|*args| self.fail(*args) }
end