Skip to content

Instantly share code, notes, and snippets.

@estum
Created July 27, 2017 08:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save estum/61b69235f7921a9515490994bf59ca8e to your computer and use it in GitHub Desktop.
Save estum/61b69235f7921a9515490994bf59ca8e to your computer and use it in GitHub Desktop.
yieldable.rb: Memoized #to_proc for any callable object to use as a block argument with the "&" operator.
# This module can be used to easy make callable object or class
# to be used as a block argument with the <tt>&</tt> operator. It just
# implements the +to_proc+ method.
#
# == Examples:
#
# class Foo
# extend Yieldable
#
# def self.call(key, value)
# "#{key}:#{value}"
# end
#
# # … or …
#
# def self.to_proc
# -> ((key, value)) { "#{key}:#{value}" }
# end
# end
#
# { a: 1, b: 2 }.map(&Foo) # => ["a:1", "b:2"]
#
# # You should prepend for instance methods.
# class Bar
# prepend Yieldable
# end
module Yieldable
# Prepends this module to a singleton class of the target instead of
# extending the target directly.
def self.extend_object(base)
prepend_features base.singleton_class
extended base
base
end
# :call-seq:
# to_proc -> a_proc
#
# Generates and memoizes a proc on the first call. By default a proc
# will invoke the +call+ method with arguments wrapped into a single array.
# You can define a custom generation of a proc in the +to_proc+ method.
def to_proc
if defined?(@_yieldable)
@_yieldable
else
@_yieldable = defined?(super) ? super : UNBOUND_PROC.curry(2)[self].freeze
end
end
UNBOUND_PROC = -> (yieldable, *args, &block) do # :nodoc:
fail NotImplementedError, "Undefined method `call` for #{yieldable.inspect}" unless defined?(yieldable.call)
args = args[0] if args.size == 1 && args[0].is_a?(Array)
block = args.pop if !block_given? && args[-1].respond_to?(:to_proc)
yieldable.call(*args, &block)
end.freeze
private_constant :UNBOUND_PROC
end
@estum
Copy link
Author

estum commented Jul 27, 2017

Examples

class Foo
  extend Yieldable

  def self.call(key, value)
    "#{key}:#{value}"
  end

  # or the same:

  def self.to_proc
    -> ((key, value)) { "#{key}:#{value}" }
  end
end
{ a: 1, b: 2 }.map(&Foo) 
# => ["a:1", "b:2"]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment