Created
November 30, 2014 06:51
-
-
Save jneen/00b3882d3240e959e29c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Variant | |
class Caser | |
attr_reader :value | |
def initialize(variant) | |
@variant = variant | |
@invoked = false | |
end | |
def try_case(tag, &b) | |
if @variant.tag == tag | |
@invoked = true | |
@value = yield(*@variant.values) | |
end | |
end | |
def invoked? | |
@invoked | |
end | |
end | |
def self.spec | |
@spec ||= {} | |
end | |
def self.caser | |
@caser ||= Class.new(Caser) | |
end | |
attr_reader :values | |
def initialize(*values) | |
@values = values | |
end | |
def self.variant(tag, *names) | |
parent_class = self | |
klass = spec[tag] = Class.new(self) | |
klass.class_eval do | |
names.each_with_index do |name, i| | |
define_method(name) { @values[i] } | |
end | |
define_method(:type) { parent_class } | |
define_method(:tag) { tag } | |
end | |
caser.class_eval do | |
define_method(tag) { |&b| try_case(tag, &b) } | |
end | |
(class << self; self; end).class_eval do | |
define_method(tag) { |*values| klass.new(*values) } | |
end | |
end | |
def inspect | |
"#<#{type}.#{tag}(#{values.map(&:inspect).join(', ')})>" | |
end | |
def cases(cases={}, &b) | |
if block_given? | |
caser = self.class.caser.new(self) | |
yield caser | |
if caser.invoked? | |
caser.value | |
else | |
non_exhaustive_cases! | |
end | |
else | |
selection = cases[tag] || cases[:else] || non_exhaustive_cases! | |
selection.call(*values) | |
end | |
end | |
end | |
class Order < Variant | |
variant :delivery, :address | |
variant :digital, :email | |
variant :pickup, :store_id | |
end | |
# n constructors | |
# Order.delivery('123 Main St') | |
# Order.digital('[email protected]') | |
# Order.pickup(456) | |
# | |
# order.cases( | |
# delivery: ->(address) { "delivering to #{address}" }, | |
# digital: ->(email) { "emailing to #{email}" }, | |
# pickup: ->(store_id) { | |
# "picking up from #{Store.find(store_id).address}" | |
# }, | |
# ) | |
# | |
# order.cases do |c| | |
# c.delivery { |address| "delivering to #{address}" } | |
# c.digital { |email| "emailing to #{email}" } | |
# c.pickup do |store_id| | |
# "picking up from #{Store.find(store_id).address}" | |
# end | |
# end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment