Last active
September 18, 2018 03:21
-
-
Save searls/b68f8ce12089b053ec22b2dae35722f7 to your computer and use it in GitHub Desktop.
Was chatting with @mfeathers about retaining Ruby's chained Enumerable style, but finding a way to inject names that reflects the application domain (as opposed to just littering functional operations everywhere, which may be seen as a sort of Primitive Obsession)
This file contains 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
# A little toy file demonstrating how to build chainable | |
# data transformations that reveal some amount of intent | |
# through named extracted methods. | |
# | |
# Kudos to @mfeathers for giving me the idea to try this | |
# | |
# Copyright Test Double, LLC, 2016. All Rights Reserved. | |
require_relative "marketing_refinements" | |
class MarketResearch | |
# Vanilla / Anonymous / Primitive approach to chaining | |
def income_by_smoking(data) | |
Hash[ | |
data.reject {|p| p[:income] < 10_000 }. | |
group_by { |p| p[:smoker] }. | |
map { |(is_smoker, people)| | |
[ | |
is_smoker ? :smokers : :non_smokers, | |
people.map {|p| p[:income]}.reduce(:+).to_f / people.size | |
] | |
} | |
] | |
end | |
# Refined approach to tacking named domain abstractions onto Array/Hash | |
using MarketingRefinements | |
def income_by_smoking_fancy(data) | |
data.exclude_incomes_under(10_000). | |
separate_people_by(:smoker). | |
average_income_by_smoking | |
end | |
end | |
DATA = [ | |
{age: 19, smoker: false, income: 10_000, education: :high_school}, | |
{age: 49, smoker: true, income: 120_000, education: :bachelors}, | |
{age: 55, smoker: false, income: 400_000, education: :masters}, | |
{age: 23, smoker: true, income: 10_000, education: :bachelors}, | |
{age: 70, smoker: false, income: 70_000, education: :phd }, | |
{age: 34, smoker: false, income: 90_000, education: :masters}, | |
{age: 90, smoker: true, income: 0, education: :high_school}, | |
] | |
original_result = MarketResearch.new.income_by_smoking(DATA) | |
fancy_result = MarketResearch.new.income_by_smoking_fancy(DATA) | |
puts <<-MSG | |
Original result: #{original_result} | |
Fancy result: #{fancy_result} | |
MSG |
This file contains 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
module MarketingRefinements | |
refine Array do | |
# Domain-specific | |
def exclude_incomes_under(min) | |
reject {|p| p[:income] < min } | |
end | |
def separate_people_by(attribute) | |
group_by { |p| p[attribute] } | |
end | |
# General-purpose | |
def average(attr) | |
map {|el| el[attr]}.reduce(:+).to_f / size | |
end | |
end | |
refine Hash do | |
# Domain-specific | |
def average_income_by_smoking | |
Hash[ | |
transform_keys { |is_smoker| | |
is_smoker ? :smokers : :non_smokers | |
}.map {|key, people| | |
[key, people.average(:income)] | |
} | |
] | |
end | |
# General-purpose | |
def transform_keys | |
{}.tap do |result| | |
self.each_key do |key| | |
result[yield(key)] = self[key] | |
end | |
end | |
end | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment