I am given a "duck type" of a single method (a "functional interface") and I have a whole flock of adapters as methods and blocks.
For example, the protocol defined by the library looks like this:
class Grouper
# group together an array of things into
# an array of arrays of things
# (that is, an array of _groups_ of things)
def group_together(things); end
end
The contract for group_together
is simple enough that I can define a bunch of them as simple methods.
module CommonGroupers
def one_per_group(things)
things.map {|x| x}
end
def one_group(things)
[things.dup]
end
def geometric(things)
result = []
things.each_with_index do |thing, i|
result << [] if i & (i + 1) == 0
result[-1] << thing
end
result
end
def sorted_by(delegate, &block)
lambda do |things|
delegate.group_together(things.sort_by(&block))
end
end
def partition_by(delegate, &block)
lambda do |things|
...
end
end
...
end
Is there a shorthand way of making these methods adhere to the Grouper
protocol?
Here are two options that I have so far. Is there is something built-in or more idiomatic?
class ProcGrouper
def initialize(&block)
@proc = block
end
def group_together(things)
@proc.call(things)
end
end
module CommonGroupers
def geometric
ProcGrouper.new do |things|
result = []
things.each_with_index do |thing, i|
result << [] if i & (i + 1) == 0
result[-1] << thing
end
result
end
end
end
CommonGroupers.geometric.group_together 1..10
or
def grouper(&block)
class << block
alias_method :group_together, :call
end
block
end
module CommonGroupers
def geometric
grouper do |things|
result = []
things.each_with_index do |thing, i|
result << [] if i & (i + 1) == 0
result[-1] << thing
end
result
end
end
end
CommonGroupers.geometric.group_together 1..10
I am still quite unclear on the implementation or use case suggested here is but as a general suggestion I guess you could go with something like
module Functions
def self.grouper_method(name,&body)
body.define_singleton_method(:group_together) {|things| call(things) }
define_singleton_method(name) { body }
end
grouper_method(:one_group) {|things| [things.dup] }
grouper_method(:geometric) do |things|
things.each_with_index.with_object([]) do |(thing, i), result|
result << [] if i & (i + 1) == 0
result.last << thing
end
end
# ETC
end
Functions.geometric.group_together 1..10
#=> [[1], [2, 3], [4, 5, 6, 7], [8, 9, 10]]
If you really wanted to implement these as functionesque you could pass a target specification as well such that these methods could be defined in a different scope than the module that defines make_function
, for instance a global Object
scope if desired.(not recommended)
module Functions
def self.grouper_method(target,name,&body)
body.define_singleton_method(:group_together) {|things| call(things) }
target.define_singleton_method(name) { body }
end
end
Functions.grouper_method(self, :geometric) do |things|
result = []
things.each_with_index do |thing, i|
result << [] if i & (i + 1) == 0
result[-1] << thing
end
result
end
geometric.group_together 1..10
#=> [[1], [2, 3], [4, 5, 6, 7], [8, 9, 10]]