Method: ADT.operation

Defined in:
lib/adt.rb

.operation(sym, &definitions) ⇒ Object

Defines an operation (method) for an ADT, using a DSL similar to the cases definition.

For each case in the adt, the block should call a method of the same name, and pass it a block argument that represents the implementation of the operation for that case.

eg. To define an operation on a Maybe/Option type which returns the wrapped value, or the supplied argument if it doesn’t have anything:

class Maybe
  extend ADT
  cases do
    just(:value)
    nothing
  end

  operation :or_value do |if_nothing|
    just { |value| value }
    nothing { if_nothing }
  end
end

Parameters:

  • The (Symbol)

    name of the operations to define.

  • The (Proc)

    definitions of the implementations for each case.



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/adt.rb', line 224

def operation(sym, &definitions)
  define_method(sym) do |*args|
    the_instance = self
    dsl = CaseRecorder.new
    # The definitions block needs to be executed in the context of the recorder, to
    # read the impls.
    dsl.__instance_exec(*args, &definitions)
    # Now we just turn the [(case_name, impl)] structure into an argument for fold and
    # are done. Fold with a hash will check that all keys are defined.
    fold(dsl._implementations.inject({}) { |memo, (c, impl)| 
      # Fucker. if 'impl' is used directly, because of the 'define_method' from earlier,
      # it is evaluated in the context of the recorder, which is bad. So instead. We
      # instance_exec it back on the instance.
      # TODO: use the proc builder like in the `cases` method, which will let us tie 
      # down the arity
      some_impl = lambda { |*args| the_instance.instance_exec(*args, &impl) }
      memo[c] = some_impl
      memo 
    })
  end
end