1+ require File . expand_path ( File . dirname ( __FILE__ ) + '/neo' )
2+
3+ class AboutPatternMatching < Neo ::Koan
4+
5+ def test_pattern_may_not_match
6+ begin
7+ case [ true , false ]
8+ in [ a , b ] if a == b # The condition after pattern is called guard.
9+ :match
10+ end
11+ rescue Exception => ex
12+ # What exception has been caught?
13+ assert_equal __ , ex . class
14+ end
15+ end
16+
17+ def test_we_can_use_else
18+ result = case [ true , false ]
19+ in [ a , b ] if a == b
20+ :match
21+ else
22+ :no_match
23+ end
24+
25+ assert_equal __ , result
26+ end
27+
28+ # ------------------------------------------------------------------
29+
30+ def value_pattern ( variable )
31+ case variable
32+ in 0
33+ :match_exact_value
34+ in 1 ..10
35+ :match_in_range
36+ in Integer
37+ :match_with_class
38+ else
39+ :no_match
40+ end
41+ end
42+
43+ def test_value_pattern
44+ assert_equal __ , value_pattern ( 0 )
45+ assert_equal __ , value_pattern ( 5 )
46+ assert_equal __ , value_pattern ( 100 )
47+ assert_equal __ , value_pattern ( 'Not a Number!' )
48+ end
49+
50+ # ------------------------------------------------------------------
51+ # This pattern will bind variable to the value
52+
53+ def variable_pattern_with_binding ( variable )
54+ case 0
55+ in variable
56+ variable
57+ else
58+ :no_match
59+ end
60+ end
61+
62+ def test_variable_pattern_with_binding
63+ assert_equal __ , variable_pattern_with_binding ( 1 )
64+ end
65+
66+ # ------------------------------------------------------------------
67+
68+ # We can pin the value of the variable with ^
69+
70+ def variable_pattern_with_pin ( variable )
71+ case 0
72+ in ^variable
73+ variable
74+ else
75+ :no_match
76+ end
77+ end
78+
79+ def test_variable_pattern_with_pin
80+ assert_equal __ , variable_pattern_with_pin ( 1 )
81+ end
82+
83+ # ------------------------------------------------------------------
84+
85+ # We can drop values from pattern
86+
87+ def pattern_with_dropping ( variable )
88+ case variable
89+ in [ _ , 2 ]
90+ :match
91+ else
92+ :no_match
93+ end
94+ end
95+
96+ def test_pattern_with_dropping
97+ assert_equal __ , pattern_with_dropping ( [ 'I will not be checked' , 2 ] )
98+ assert_equal __ , pattern_with_dropping ( [ 'I will not be checked' , 'But I will!' ] )
99+ end
100+
101+ # ------------------------------------------------------------------
102+
103+ # We can use logical *or* in patterns
104+
105+ def alternative_pattern ( variable )
106+ case variable
107+ in 0 | false | nil
108+ :match
109+ else
110+ :no_match
111+ end
112+ end
113+
114+ def test_alternative_pattern
115+ assert_equal __ , alternative_pattern ( 0 )
116+ assert_equal __ , alternative_pattern ( false )
117+ assert_equal __ , alternative_pattern ( nil )
118+ assert_equal __ , alternative_pattern ( 4 )
119+ end
120+
121+ # ------------------------------------------------------------------
122+
123+ # As pattern binds the variable to the value if pattern matches
124+ # pat: pat => var
125+
126+ def as_pattern
127+ a = 'First I was afraid'
128+
129+ case 'I was petrified'
130+ in String => a
131+ a
132+ else
133+ :no_match
134+ end
135+ end
136+
137+ def test_as_pattern
138+ assert_equal __ , as_pattern
139+ end
140+
141+ # ------------------------------------------------------------------
142+
143+ # Array pattern works with all objects that have #deconstruct method that returns Array
144+ # It is useful to cut needed parts from Array-ish objects
145+
146+ class Deconstructible
147+ def initialize ( str )
148+ @data = str
149+ end
150+
151+ def deconstruct
152+ @data &.split ( '' )
153+ end
154+ end
155+
156+ def array_pattern ( deconstructible )
157+ case deconstructible
158+ in 'a' , *res , 'd'
159+ res
160+ else
161+ :no_match
162+ end
163+ end
164+
165+ def test_array_pattern
166+ assert_equal __ , array_pattern ( Deconstructible . new ( 'abcd' ) )
167+ assert_equal __ , array_pattern ( Deconstructible . new ( '123' ) )
168+ end
169+
170+ # ------------------------------------------------------------------
171+
172+ # Hash pattern is quite the same as Array pattern, but it expects #deconsturct_keys(keys) method
173+ # It works with symbol keys for now
174+
175+ class LetterAccountant
176+ def initialize ( str )
177+ @data = str
178+ end
179+
180+ def deconstruct_keys ( keys )
181+ # we will count number of occurrences of each key in our data
182+ keys . map { |key | [ key , @data . count ( key . to_s ) ] } . to_h
183+ end
184+ end
185+
186+ def hash_pattern ( deconstructible_as_hash )
187+ case deconstructible_as_hash
188+ in { a : a , b : b }
189+ [ a , b ]
190+ else
191+ :no_match
192+ end
193+ end
194+
195+ def test_hash_pattern
196+ assert_equal __ , hash_pattern ( LetterAccountant . new ( 'aaabbc' ) )
197+ assert_equal __ , hash_pattern ( LetterAccountant . new ( 'xyz' ) )
198+ end
199+
200+ # we can write it even shorter
201+ def hash_pattern_with_sugar ( deconstructible_as_hash )
202+ case deconstructible_as_hash
203+ in a :, b :
204+ [ a , b ]
205+ else
206+ :no_match
207+ end
208+ end
209+
210+ def test_hash_pattern_with_sugar
211+ assert_equal __ , hash_pattern_with_sugar ( LetterAccountant . new ( 'aaabbc' ) )
212+ assert_equal __ , hash_pattern_with_sugar ( LetterAccountant . new ( 'xyz' ) )
213+ end
214+
215+ end
0 commit comments