- a Clojure demo
- about "FP in Ruby"
there are two key concepts that drive everything else in Clojure: simplicity and power.
a thing is simple if it is not compound
# Everybody knows what + means, right?
1 + 2
# => 3
# Everybody knows what + means, right?
1 + 2
# => 3
ruby + clojure
# => ???????
#
# what is ruby in this context?
# what is clojure in this context?
# what does it mean to add them?
def initialize(inclusion_patterns=nil, exclusion_patterns=DEFAULT_EXCLUSION_PATTERNS.dup)
@exclusion_patterns = exclusion_patterns
if inclusion_patterns.nil?
@inclusion_patterns = matches_an_exclusion_pattern?(Dir.getwd) ?
[Regexp.new(Dir.getwd)] : []
else
@inclusion_patterns = inclusion_patterns
end
end
def exclude?(line)
@inclusion_patterns.none? {|p| p =~ line} and matches_an_exclusion_pattern?(line)
end
def matches_an_exclusion_pattern?(line)
@exclusion_patterns.any? {|p| p =~ line}
end
def initialize(inclusion_patterns=nil, exclusion_patterns=DEFAULT_EXCLUSION_PATTERNS.dup)
@exclusion_patterns = exclusion_patterns
@inclusion_patterns = inclusion_patterns ||
(matches_an_exclusion_pattern?(Dir.getwd) ? [Regexp.new(Dir.getwd)] : [])
end
def exclude?(line)
@inclusion_patterns.none? {|p| p =~ line} and matches_an_exclusion_pattern?(line)
end
def matches_an_exclusion_pattern?(line)
@exclusion_patterns.any? {|p| p =~ line}
end
def initialize(inclusion_patterns=nil, exclusion_patterns=nil)
@exclusion_patterns = exclusion_patterns || DEFAULT_EXCLUSION_PATTERNS.dup
@inclusion_patterns = inclusion_patterns ||
(matches_an_exclusion_pattern?(Dir.getwd) ? [Regexp.new(Dir.getwd)] : [])
end
def exclude?(line)
@inclusion_patterns.none? {|p| p =~ line} and matches_an_exclusion_pattern?(line)
end
def matches_an_exclusion_pattern?(line)
@exclusion_patterns.any? {|p| p =~ line}
end
# boom!
def initialize(inclusion_patterns=nil, exclusion_patterns=nil)
@inclusion_patterns = inclusion_patterns ||
(matches_an_exclusion_pattern?(Dir.getwd) ? [Regexp.new(Dir.getwd)] : [])
# ▲▲▲ has an order dependency on ▼▼▼
@exclusion_patterns = exclusion_patterns || DEFAULT_EXCLUSION_PATTERNS.dup)
end
def exclude?(line)
@inclusion_patterns.none? {|p| p =~ line} and matches_an_exclusion_pattern?(line)
end
def matches_an_exclusion_pattern?(line)
@exclusion_patterns.any? {|p| p =~ line}
end
def initialize(inclusion_patterns=nil, exclusion_patterns=nil)
@exclusion_patterns = exclusion_patterns || DEFAULT_EXCLUSION_PATTERNS.dup)
@inclusion_patterns = inclusion_patterns ||
(@exclusion_patterns.any? {|p| p =~ Dir.getwd} ? [Regexp.new(Dir.getwd)] : [])
end
def exclude?(line)
@inclusion_patterns.none? {|p| p =~ line} and matches_an_exclusion_pattern?(line)
end
def matches_an_exclusion_pattern?(line)
@exclusion_patterns.any? {|p| p =~ line}
end
def initialize(exclusion_patterns=nil, inclusion_patterns=nil)
@exclusion_patterns = exclusion_patterns || DEFAULT_EXCLUSION_PATTERNS.dup)
@inclusion_patterns = inclusion_patterns ||
(@exclusion_patterns.any? {|p| p =~ Dir.getwd} ? [Regexp.new(Dir.getwd)] : [])
end
def exclude?(line)
@inclusion_patterns.none? {|p| p =~ line} and matches_an_exclusion_pattern?(line)
end
def matches_an_exclusion_pattern?(line)
@exclusion_patterns.any? {|p| p =~ line}
end
def initialize(exclusion_patterns=nil, inclusion_patterns=nil)
@exclusion_patterns = exclusion_patterns || DEFAULT_EXCLUSION_PATTERNS.dup)
@inclusion_patterns = inclusion_patterns ||
(@exclusion_patterns.any? {|p| p =~ Dir.getwd} ? [Regexp.new(Dir.getwd)] : [])
end
def exclude?(line)
@inclusion_patterns.none? {|p| p =~ line} and @exclusion_patterns.any? {|p| p =~ line}
end
def initialize(exclusion_patterns=nil, inclusion_patterns=nil)
@exclusion_patterns = exclusion_patterns || DEFAULT_EXCLUSION_PATTERNS.dup)
@inclusion_patterns = inclusion_patterns ||
(@exclusion_patterns.any? {|p| p =~ Dir.getwd} ? [Regexp.new(Dir.getwd)] : [])
end
def exclude?(line)
@exclusion_patterns.any? {|p| p =~ line} and @inclusion_patterns.none? {|p| p =~ line}
end
# operand operator operand 1 + 2 37 + 42 add(1,2) add(37,42)
# object.method(arg)
special_formatter.format("this string")
special_formatter.format("that string")
specially_format("this string")
specially_format("that string")
# object.method { |first_operand| first_operand operator second_operand }
@exclusion_patterns.any? {|p| p =~ Dir.getwd}
@exclusion_patterns.any? {|p| p =~ line}
# extract method wrapping entire expression
any_exclusion_patterns_match?(Dir.getwd)
any_exclusion_patterns_match?(line)
any_exclusion_patterns with @exclusion_patterns.any?match? representing the block content
def initialize(exclusion_patterns=nil, inclusion_patterns=nil)
@exclusion_patterns = exclusion_patterns || DEFAULT_EXCLUSION_PATTERNS.dup)
@inclusion_patterns = inclusion_patterns ||
(@exclusion_patterns.any?(&match?(Dir.getwd)) ? [Regexp.new(Dir.getwd)] : [])
end
def exclude?(line)
@exclusion_patterns.any?(&match?(line)) && @inclusion_patterns.none?(&match?(line))
end
def match?(string)
lambda {|re| re =~ str}
end
def match?(str)
lambda {|re| re =~ str}
end
# use with any iterator
patterns.any? &match?(str)
patterns.none? &match?(str)
patterns.select &match?(str)
patterns.reject &match?(str)
@exclusion_patterns.any? {|p| p =~ line}
@exclusion_patterns.any? {|p| p.match(line)}
@exclusion_patterns.any? &match?(line)
any_exclusion_patterns_match?(line)
def initialize(exclusion_patterns=nil, inclusion_patterns=nil)
@exclusion_patterns = exclusion_patterns || DEFAULT_EXCLUSION_PATTERNS.dup)
@inclusion_patterns = inclusion_patterns ||
(@exclusion_patterns.any? {|p| p =~ Dir.getwd} ? [Regexp.new(Dir.getwd)] : [])
end
def exclude?(line)
@exclusion_patterns.any? {|p| p =~ line} and @inclusion_patterns.none? {|p| p =~ line}
end
(1.day + 1.year + 2.months + 2.days).inspect
# => "1 year, 2 months and 3 days"
# Internally, (1.day + 1.year + 2.months + 2.days) is represented as
# [[:days, 1], [:years, 1], [:months, 2], [:days, 2]]
(defn format-duration [parts]
(->> parts
(map (partial apply hash-map))
(apply merge-with +)
(sort-by order-of-units)
(map format-unit)
(map format-unit-val-pair)
(to-sentence)))
(format-duration [[:days 1] [:years 1] [:months 2] [:days 2]])
; => "1 year, 2 months and 3 days"
(defn format-duration [parts]
(->> parts
(map (partial apply hash-map))
(apply merge-with +)
(sort-by (fn [[u _]] (.indexOf [:years, :months :days :hours :minutes :seconds] u)))
(map (fn [[u v]] (if (= 1 v) [v (chop (name u))] [v (name u)])))
(map (partial clojure.string/join " "))
(to-sentence)))
(format-duration [[:days 1] [:years 1] [:months 2] [:days 2]])
; => "1 year, 2 months and 3 days"
(defn format-duration [parts]
(->> parts
; [[:days 1] [:years 1] [:months 2] [:days 2]]
(map (partial apply hash-map))
; [{:days 1} {:years 1} {:months 2} {:days 2}]
(apply merge-with +)
; {:days 3 :years 1 :months 2}
(sort-by (fn [[u _]] (.indexOf [:years :months :days :hours :minutes :seconds] u)))
; [[:years 1] [:months 2] [:days 3]]
(map (fn [[u v]] (if (= 1 v) [v (chop (name u))] [v (name u)])))
; [[1 "year"] [2 "months" 2] [3 "days"]]
(map (partial clojure.string/join " "))
; ["1 year" "2 months" "3 days"]
(to-sentence)))
; "1 year, 2 months and 3 days"
(format-duration [[:days 1] [:years 1] [:months 2] [:days 2]])
# Duration#inspect
def inspect
consolidated = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }
parts = [:years, :months, :days, :minutes, :seconds].map do |length|
n = consolidated[length]
"#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero?
end.compact
parts = ["0 seconds"] if parts.empty?
parts.to_sentence(:locale => :en)
end
# Duration#inspect mid-refactoring
def inspect
val_for = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }
[:years, :months, :days, :minutes, :seconds].
select {|unit| val_for[unit].nonzero? }.
map {|unit| [unit, val_for[unit]]}.
map {|unit, val| "#{val} #{val == 1 ? unit.to_s.singularize : unit.to_s}"}.
tap {|units| units << "0 seconds" if units.empty?}.
to_sentence(:locale => :en)
end
# Duration#inspect refactored
def inspect
parts.
reduce(::Hash.new(0)) {|h,(unit,val)| h[unit] += val; h}.
sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}.
map {|unit,val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
to_sentence(:locale => :en)
end
chop performs 6x better than singularize
# Duration#inspect refactored
def inspect
parts.
# [[:days, 1], [:years, 1], [:months, 2], [:days, 2]]
reduce(::Hash.new(0)) {|h,(unit,val)| h[unit] += val; h}.
# {:days => 3, :years => 1, :months => 2}
sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}.
# [[:years, 1], [:months, 2], [:days, 3]]
map {|unit,val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
# ["1 year", "2 months", "3 days"]
to_sentence(:locale => :en)
# "1 year, 2 months and 3 days"
end
Enumerable.public_instance_methods.sort - Object.new.methods # => [:all?, :any?, :chunk, :collect, :collect_concat, :count, :cycle, :detect, :drop, # :drop_while, :each_cons, :each_entry, :each_slice, :each_with_index, # :each_with_object, :entries, :find, :find_all, :find_index, :first, :flat_map, # :grep, :group_by, :include?, :inject, :lazy, :map, :max, :max_by, :member?, # :min, :min_by, :minmax, :minmax_by, :none?, :one?,:partition, :reduce, # :reject, :reverse_each, :select, :slice_before, :sort, :sort_by, :take, # :take_while, :to_a, :zip]
Enumerable provides a comprehensive language for data transformations
; using https://github.com/jaycfields/expectations
(expect "John Doe" (full-name (make-person "John" "Doe")))
describe Person do
describe "#full_name" do
it "concats first and last names" do
# ...
rspec --format documentation
Person
#full_name
concats first and last names
handles blank first name gracefully
handles blank last name gracefully
raises when missing both first and last name
describe Person do
it "concats first and last names to provide full_name" do
person = Person.new("John", "Doe")
# rspec-expectations with should
person.full_name.should eq "John Doe"
# rspec-expectations with expect
expect(person.full_name).to eq "John Doe"
# minitest/test
assert_equal "John Doe", person.full_name
# minitest/spec
person.full_name.must_equal "John Doe"
end
end
describe Person do
it "concats first and last names to provide full_name" do
person = Person.new("John", "Doe")
assert { person.full_name == "John Doe" }
end
end
describe Person do
describe "#full_name" do
Given(:person) { Person.new("John", "Doe") }
Then { person.full_name == "John Doe" }
end
end
describe Person do
describe "#full_name" do
context "with first and last name supplied" do
Given(:person) { Person.new("John", "Doe") }
Then { person.full_name == "John Doe" }
end
context "with nil first name" do
Given(:person) { Person.new(nil, "Doe") }
Then { person.full_name == "Doe" }
end
context "with blank first name" do
Given(:person) { Person.new("", "Doe") }
Then { person.full_name == "Doe" }
end
context "with a different kind of blank first name" do
Given(:person) { Person.new(" ", "Doe") }
Then { person.full_name == "Doe" }
end
context "with nil last name" do
Given(:person) { Person.new("John", nil) }
Then { person.full_name == "John" }
end
end
end
describe "Person#full_name" do
it "concats first and last names" do
assert { Person.new("John", "Doe").full_name == "John Doe" }
end
it "handles nils and blanks gracefully" do
assert { Person.new(nil, "Doe").full_name == "Doe" }
assert { Person.new("", "Doe").full_name == "Doe" }
assert { Person.new(" ", "Doe").full_name == "Doe" }
assert { Person.new("John", nil).full_name == "John" }
assert { Person.new("John", "" ).full_name == "John" }
assert { Person.new("John", " ").full_name == "John" }
end
end
describe "Person#full_name" do
it "concats first and last names" do
assert { Person.new("John", "Doe").full_name == "John Doe" }
end
it "handles nil or blank first_name gracefully" do
assert { Person.new(nil, "Doe").full_name == "Doe" }
assert { Person.new("", "Doe").full_name == "Doe" }
assert { Person.new(" ", "Doe").full_name == "Doe" }
end
it "handles nil or blank last_name gracefully" do
assert { Person.new("John", nil).full_name == "John" }
assert { Person.new("John", "" ).full_name == "John" }
assert { Person.new("John", " ").full_name == "John" }
end
end
describe "Person#full_name" do
it "concats first and last names" do
assert { Person.new("John", "Doe").full_name == "John Doe" }
end
it "handles nils gracefully" do
assert { Person.new(nil, "Doe").full_name == "Doe" }
assert { Person.new("John", nil).full_name == "John" }
end
it "handles blanks gracefully" do
assert { Person.new("", "Doe").full_name == "Doe" }
assert { Person.new(" ", "Doe").full_name == "Doe" }
assert { Person.new("John", "" ).full_name == "John" }
assert { Person.new("John", " ").full_name == "John" }
end
end
describe "Person#full_name" do
it "concats first and last names" do
assert { Person.new("John", "Doe").full_name == "John Doe" }
end
it "handles nil first_name gracefully" do
assert { Person.new(nil, "Doe").full_name == "Doe" }
end
it "handles nil last_name gracefully" do
assert { Person.new("John", nil).full_name == "John" }
end
it "handles blank first_name gracefully" do
assert { Person.new("", "Doe").full_name == "Doe" }
assert { Person.new(" ", "Doe").full_name == "Doe" }
do
it "handles blank last_name gracefully" do
assert { Person.new("John", "" ).full_name == "John" }
assert { Person.new("John", " ").full_name == "John" }
end
end
# DOES NOT EXIST YET. SOMEBODY PLEASE MAKE IT SO!
describe "Person#full_name" do
it "concats first and last names" do
example { Person.new("John", "Doe").full_name == "John Doe" }
end
it "handles nils and blanks gracefully" do # description: general
example { Person.new(nil, "Doe").full_name == "Doe" }
example { Person.new("", "Doe").full_name == "Doe" } # examples: specific
example { Person.new(" ", "Doe").full_name == "Doe" }
example { Person.new("John", nil).full_name == "John" }
example { Person.new("John", "" ).full_name == "John" }
example { Person.new("John", " ").full_name == "John" }
end
end
Person#full_name concats first and last names handles nils and blanks gracefully
Person#full_name
concats first and last names
example at ./person_spec.rb:6 (FAILED - 1)
handles nils and blanks gracefully
Failures:
1) Person#full_name
Failure/Error: example { assert { person.full_name == "John Doe" } }
Expected (person.full_name == "John Doe"), but
person.full_name is "JohnDoe"
# ./person_spec.rb:6:in `block (3 levels) in <top (required)>'