- One of the outstanding features of Swift is that many of the original features are implemented by Swift instead of having to customize the hardware in complier. In theory, it is very convenient and gives us flexibility in customizing the way that works effectively for work.
- In this article we will delve deeper into choosing and customizing appropriate patterns in Swift to see how we can build complete custom patterns and some interesting techniques we can use. after that.
1 / Simple example:
Pattern matching
is the work of matching values with predefinedpattern
usually to determine which branch of code will perform the job. For example, every time weswitch
based on the available values:
1 2 3 4 5 6 7 8 9 | <span class="token keyword">func</span> <span class="token function">items</span> <span class="token punctuation">(</span> <span class="token keyword">in</span> section <span class="token punctuation">:</span> <span class="token builtin">Section</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token punctuation">[</span> <span class="token builtin">Item</span> <span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> section <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token punctuation">.</span> featured <span class="token punctuation">:</span> <span class="token keyword">return</span> dataSource <span class="token punctuation">.</span> featuredItems <span class="token keyword">case</span> <span class="token punctuation">.</span> recent <span class="token punctuation">:</span> <span class="token keyword">return</span> dataSource <span class="token punctuation">.</span> recentItems <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Above we used the enum
Section
to identify two previously createdpattern
(featured
andrecent
), this is a common use forpattern matching
in swift, it sometimes causes undesirable effects in when the code. - To make it clear, we have defined the struct
Pattern
, we will use it to define ourclosure
. ThisClosure
will take the appropriate value and return theBool
result:
1 2 3 4 | <span class="token keyword">struct</span> <span class="token builtin">Pattern</span> <span class="token operator"><</span> <span class="token builtin">Value</span> <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> closure <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token builtin">Value</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Bool</span> <span class="token punctuation">}</span> |
struct
abovestruct
may seem simple, but it completely allows us to define custompattern
arrangements by usinggeneric type constaints
to addstatic factory methods
to create ourpattern
.
1 2 3 4 5 6 | <span class="token keyword">extension</span> <span class="token builtin">Pattern</span> <span class="token keyword">where</span> <span class="token builtin">Value</span> <span class="token punctuation">:</span> <span class="token builtin">Hashable</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token keyword">func</span> <span class="token function">any</span> <span class="token punctuation">(</span> of candidates <span class="token punctuation">:</span> <span class="token builtin">Set</span> <span class="token operator"><</span> <span class="token builtin">Value</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> candidates <span class="token punctuation">.</span> <span class="token function">contains</span> <span class="token punctuation">(</span> $ <span class="token number">0</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- All
pattern matching
inSwift
is very powerful, with operator~=
used as the left argument and the right values.
1 2 3 4 | <span class="token keyword">func</span> <span class="token operator">~</span> <span class="token operator">=</span> <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">(</span> lhs <span class="token punctuation">:</span> <span class="token builtin">Pattern</span> <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">,</span> rhs <span class="token punctuation">:</span> T <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Bool</span> <span class="token punctuation">{</span> lhs <span class="token punctuation">.</span> <span class="token function">closure</span> <span class="token punctuation">(</span> rhs <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
2 / Mix and customize accordingly:
- If we are working in an app as a social network, when using the
LoggedInUser
struct to track the date of the logged-in user as id, friendId:
1 2 3 4 5 6 | <span class="token keyword">struct</span> <span class="token builtin">LoggedInUser</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> id <span class="token punctuation">:</span> <span class="token builtin">Identifier</span> <span class="token operator"><</span> <span class="token builtin">User</span> <span class="token operator">></span> <span class="token keyword">var</span> friendIDs <span class="token punctuation">:</span> <span class="token builtin">Set</span> <span class="token operator"><</span> <span class="token builtin">Identifier</span> <span class="token operator"><</span> <span class="token builtin">User</span> <span class="token operator">></span> <span class="token operator">></span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> |
- Now we want to build a view controller to display any number of users as a list but we want to render different icons depending on the type of user. That decision is made by the
switch statement
:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token keyword">private</span> <span class="token keyword">extension</span> <span class="token builtin">UserListViewController</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">resolveIcon</span> <span class="token punctuation">(</span> <span class="token keyword">for</span> userID <span class="token punctuation">:</span> <span class="token builtin">Identifier</span> <span class="token operator"><</span> <span class="token builtin">User</span> <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Icon</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> userID <span class="token punctuation">{</span> <span class="token keyword">case</span> loggedInUser <span class="token punctuation">.</span> id <span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span> currentUser <span class="token keyword">case</span> <span class="token punctuation">.</span> <span class="token function">any</span> <span class="token punctuation">(</span> of <span class="token punctuation">:</span> loggedInUser <span class="token punctuation">.</span> friendIDs <span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span> friend <span class="token keyword">default</span> <span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span> anyUser <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
3 / Pattern comparison:
- Continue to expand the
Pattern
by adding functions. We will write extensions using theComparable
protocol, which includes twomethod
:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">extension</span> <span class="token builtin">Pattern</span> <span class="token keyword">where</span> <span class="token builtin">Value</span> <span class="token punctuation">:</span> <span class="token builtin">Comparable</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token keyword">func</span> <span class="token function">lessThan</span> <span class="token punctuation">(</span> <span class="token number">_</span> value <span class="token punctuation">:</span> <span class="token builtin">Value</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> $ <span class="token number">0</span> <span class="token operator"><</span> value <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">static</span> <span class="token keyword">func</span> <span class="token function">greaterThan</span> <span class="token punctuation">(</span> <span class="token number">_</span> value <span class="token punctuation">:</span> <span class="token builtin">Value</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> $ <span class="token number">0</span> <span class="token operator">></span> value <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- The above code will be useful when we want to compare more or less valuable values. In this example we can test if the user exceeds the game threshold by using the
switch
:
1 2 3 4 5 6 7 8 9 10 11 | func levelFinished(withScore score: Int) { switch score { case .lessThan(50): showGameOverScreen() case .greaterThan(highscore): showNewHighscore(score) default: goToNextLevel() } } |
4 / Covert key path in the patterns:
- Another use for creating extremely useful patterns is to use the
key-path
:
1 2 3 4 | <span class="token keyword">func</span> <span class="token operator">~</span> <span class="token operator">=</span> <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">(</span> lhs <span class="token punctuation">:</span> <span class="token builtin">KeyPath</span> <span class="token operator"><</span> T <span class="token punctuation">,</span> <span class="token builtin">Bool</span> <span class="token operator">></span> <span class="token punctuation">,</span> rhs <span class="token punctuation">:</span> T <span class="token operator">?</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Bool</span> <span class="token punctuation">{</span> rhs <span class="token operator">?</span> <span class="token punctuation">[</span> keyPath <span class="token punctuation">:</span> lhs <span class="token punctuation">]</span> <span class="token operator">?</span> <span class="token operator">?</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> |
- With the above code we can blend
key-path
withpattern
types that allow us to solve complex logic code just by usingswitch statement
. - Here we have decided how to parse a line of text in a list dependent on the first character by using the
Character type
to create a key-path pattern, combined with patterns that match the enumKind
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <span class="token keyword">struct</span> <span class="token builtin">ListItemParser</span> <span class="token punctuation">{</span> <span class="token keyword">enum</span> <span class="token builtin">Kind</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> numbered <span class="token keyword">case</span> unordered <span class="token punctuation">}</span> <span class="token keyword">let</span> kind <span class="token punctuation">:</span> <span class="token builtin">Kind</span> <span class="token keyword">func</span> <span class="token function">parseLine</span> <span class="token punctuation">(</span> <span class="token number">_</span> line <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">ListItem</span> <span class="token punctuation">{</span> <span class="token comment">// Here we're switching on an optional Character, which is</span> <span class="token comment">// the type of values that Swift strings are made up of:</span> <span class="token keyword">switch</span> line <span class="token punctuation">.</span> <span class="token builtin">first</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token punctuation">.</span> <span class="token keyword">none</span> <span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token builtin">Error</span> <span class="token punctuation">.</span> emptyLine <span class="token keyword">case</span> <span class="token punctuation">.</span> isNewline <span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span> empty <span class="token keyword">case</span> <span class="token punctuation">.</span> isNumber <span class="token keyword">where</span> kind <span class="token operator">==</span> <span class="token punctuation">.</span> numbered <span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token function">parseLineAsNumberedItem</span> <span class="token punctuation">(</span> line <span class="token punctuation">)</span> <span class="token keyword">case</span> <span class="token string">"-"</span> <span class="token keyword">where</span> kind <span class="token operator">==</span> <span class="token punctuation">.</span> unordered <span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token function">parseLineAsUnorderedItem</span> <span class="token punctuation">(</span> line <span class="token punctuation">)</span> <span class="token keyword">case</span> <span class="token punctuation">.</span> <span class="token function">some</span> <span class="token punctuation">(</span> <span class="token keyword">let</span> character <span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token builtin">Error</span> <span class="token punctuation">.</span> <span class="token function">invalidFirstCharacter</span> <span class="token punctuation">(</span> character <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- For the above code to work smoothly, define another operator
==
will return aPattern
combiningKeyPath
and a constant as follows:
1 2 3 4 | <span class="token keyword">func</span> <span class="token operator">==</span> <span class="token operator"><</span> T <span class="token punctuation">,</span> V <span class="token punctuation">:</span> <span class="token builtin">Equatable</span> <span class="token operator">></span> <span class="token punctuation">(</span> lhs <span class="token punctuation">:</span> <span class="token builtin">KeyPath</span> <span class="token operator"><</span> T <span class="token punctuation">,</span> V <span class="token operator">></span> <span class="token punctuation">,</span> rhs <span class="token punctuation">:</span> V <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Pattern</span> <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token builtin">Pattern</span> <span class="token punctuation">{</span> $ <span class="token number">0</span> <span class="token punctuation">[</span> keyPath <span class="token punctuation">:</span> lhs <span class="token punctuation">]</span> <span class="token operator">==</span> rhs <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Now that we have combined the
key-path
with the value generated from thepattern
we can simply implement the way we calculate the level of the delivery value with theDestination
as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <span class="token keyword">struct</span> <span class="token builtin">Destination</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> address <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token keyword">var</span> city <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token keyword">var</span> country <span class="token punctuation">:</span> <span class="token builtin">Country</span> <span class="token punctuation">}</span> <span class="token keyword">extension</span> <span class="token builtin">Destination</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> shippingCost <span class="token punctuation">:</span> <span class="token builtin">ShippingCost</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token keyword">self</span> <span class="token punctuation">{</span> <span class="token comment">// Combining a key path with a constant value:</span> <span class="token keyword">case</span> <span class="token punctuation">.</span> city <span class="token operator">==</span> <span class="token string">"Paris"</span> <span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span> free <span class="token comment">// Using a nested key path as a pattern:</span> <span class="token keyword">case</span> <span class="token punctuation">.</span> country <span class="token punctuation">.</span> isInEurope <span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span> reduced <span class="token keyword">default</span> <span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">.</span> normal <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |