- 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 contentdef 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)>'