diff --git a/src/about_pattern_matching.rb b/src/about_pattern_matching.rb new file mode 100644 index 00000000..065e100b --- /dev/null +++ b/src/about_pattern_matching.rb @@ -0,0 +1,215 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutPatternMatching < Neo::Koan + + def test_pattern_may_not_match + begin + case [true, false] + in [a, b] if a == b # The condition after pattern is called guard. + :match + end + rescue Exception => ex + # What exception has been caught? + assert_equal __, ex.class + end + end + + def test_we_can_use_else + result = case [true, false] + in [a, b] if a == b + :match + else + :no_match + end + + assert_equal __, result + end + + # ------------------------------------------------------------------ + + def value_pattern(variable) + case variable + in 0 + :match_exact_value + in 1..10 + :match_in_range + in Integer + :match_with_class + else + :no_match + end + end + + def test_value_pattern + assert_equal __, value_pattern(0) + assert_equal __, value_pattern(5) + assert_equal __, value_pattern(100) + assert_equal __, value_pattern('Not a Number!') + end + + # ------------------------------------------------------------------ + # This pattern will bind variable to the value + + def variable_pattern_with_binding(variable) + case 0 + in variable + variable + else + :no_match + end + end + + def test_variable_pattern_with_binding + assert_equal __, variable_pattern_with_binding(1) + end + + # ------------------------------------------------------------------ + + # We can pin the value of the variable with ^ + + def variable_pattern_with_pin(variable) + case 0 + in ^variable + variable + else + :no_match + end + end + + def test_variable_pattern_with_pin + assert_equal __, variable_pattern_with_pin(1) + end + + # ------------------------------------------------------------------ + + # We can drop values from pattern + + def pattern_with_dropping(variable) + case variable + in [_, 2] + :match + else + :no_match + end + end + + def test_pattern_with_dropping + assert_equal __, pattern_with_dropping(['I will not be checked', 2]) + assert_equal __, pattern_with_dropping(['I will not be checked', 'But I will!']) + end + + # ------------------------------------------------------------------ + + # We can use logical *or* in patterns + + def alternative_pattern(variable) + case variable + in 0 | false | nil + :match + else + :no_match + end + end + + def test_alternative_pattern + assert_equal __, alternative_pattern(0) + assert_equal __, alternative_pattern(false) + assert_equal __, alternative_pattern(nil) + assert_equal __, alternative_pattern(4) + end + + # ------------------------------------------------------------------ + + # As pattern binds the variable to the value if pattern matches + # pat: pat => var + + def as_pattern + a = 'First I was afraid' + + case 'I was petrified' + in String => a + a + else + :no_match + end + end + + def test_as_pattern + assert_equal __, as_pattern + end + + # ------------------------------------------------------------------ + + # Array pattern works with all objects that have #deconstruct method that returns Array + # It is useful to cut needed parts from Array-ish objects + + class Deconstructible + def initialize(str) + @data = str + end + + def deconstruct + @data&.split('') + end + end + + def array_pattern(deconstructible) + case deconstructible + in 'a', *res, 'd' + res + else + :no_match + end + end + + def test_array_pattern + assert_equal __, array_pattern(Deconstructible.new('abcd')) + assert_equal __, array_pattern(Deconstructible.new('123')) + end + + # ------------------------------------------------------------------ + + # Hash pattern is quite the same as Array pattern, but it expects #deconsturct_keys(keys) method + # It works with symbol keys for now + + class LetterAccountant + def initialize(str) + @data = str + end + + def deconstruct_keys(keys) + # we will count number of occurrences of each key in our data + keys.map { |key| [key, @data.count(key.to_s)] }.to_h + end + end + + def hash_pattern(deconstructible_as_hash) + case deconstructible_as_hash + in {a: a, b: b} + [a, b] + else + :no_match + end + end + + def test_hash_pattern + assert_equal __, hash_pattern(LetterAccountant.new('aaabbc')) + assert_equal __, hash_pattern(LetterAccountant.new('xyz')) + end + + # we can write it even shorter + def hash_pattern_with_sugar(deconstructible_as_hash) + case deconstructible_as_hash + in a:, b: + [a, b] + else + :no_match + end + end + + def test_hash_pattern_with_sugar + assert_equal __, hash_pattern_with_sugar(LetterAccountant.new('aaabbc')) + assert_equal __, hash_pattern_with_sugar(LetterAccountant.new('xyz')) + end + +end \ No newline at end of file diff --git a/src/path_to_enlightenment.rb b/src/path_to_enlightenment.rb index fa5a0914..726579c4 100644 --- a/src/path_to_enlightenment.rb +++ b/src/path_to_enlightenment.rb @@ -38,4 +38,7 @@ in_ruby_version("jruby") do require 'about_java_interop' end +in_ruby_version("2.7") do + require 'about_pattern_matching' +end require 'about_extra_credit'