Migration from existing patterns
Replacing switch
You can turn a switch
into either a match statement or a match expression, depending on its usage.
If you are using an IDE, you can use the “Refactor switch
to match
” refactoring code-action to do most of the below. To activate, the switch
needs the following properties:
- Every case of the switch must end with a
break
,return
, orthrow
, except the last case. - If there is a
default
, it must be the last case. - The
case
test must be convertible to a match pattern. - If there is a
let
orconst
in the case body, it must be wrapped in a block.
And the caveats for the resulting match:
- It may contain other
break
s (other than the last one that was removed) that will be a parse error and you will have to figure out what to do with that. - It may not be exhaustively checked, or identifier patterns may not be valid (e.g. just typed as
string
). You will get new errors you will have to resolve.
To match statement
Most switch
statements can be turned into match statements:
- Replace
switch
withmatch
- Delete the
case
- Replace the colon
:
after the case test with an arrow=>
- Wrap the case body in a block
{ ... }
- Remove the
break;
- If multiple cases share a body, use an "or" pattern
|
- Replace the
default
with a wildcard_
// Before
switch (action) {
case 'delete':
case 'remove':
data.pop();
break;
case 'add':
data.push(1);
break;
default:
show(data);
}
// After
match (action) {
'delete' | 'remove' => {
data.pop();
}
'add' => {
data.push(1);
}
_ => {
show(data);
}
}
If you are depending on the fallthrough behavior of switch
cases when not using break
(other than the simple case where the body is completely shared), then you will likely have to refactor your case body code into a function which is called.
To match expression
If every case of your switch
has a body that contains a single return
or a single assignment, then you can turn your switch
into a match expression.
For the return
case, make the same changes as in the to match statement section, except:
- Replace each case body with only the expression being returned, and delete the
return
, and don't use braces for thecase
body - Separate each case with a comma
,
- Return the entire match expression
1// Before2function getSizeBefore(imageSize: 'small' | 'medium' | 'large') {3 switch (imageSize) {4 case 'small':5 return 50;6 case 'medium':7 return 100;8 case 'large':9 return 200;10 };11}12
13// After14function getSizeAfter(imageSize: 'small' | 'medium' | 'large') {15 return match (imageSize) {16 'small' => 50,17 'medium' => 100,18 'large' => 200,19 };20}
For the assignment case, make the same changes as in the to match statement section, except:
- Replace each case body with only the expression being assigned, and don't use braces for the
case
body - Separate each case with a comma
,
- Assign the entire match expression to the variable
- If you no longer re-assign the variable, you can change it to a
const
// Before
let colorSchemeStyles;
switch (colorScheme) {
case 'darker':
colorSchemeStyles = colorSchemeDarker;
break;
case 'light':
colorSchemeStyles = colorSchemeLight;
break;
case 'unset':
colorSchemeStyles = colorSchemeDefault;
break;
}
// After
const colorSchemeStyles = match (colorScheme) {
'darker' => colorSchemeDarker,
'light' => colorSchemeLight,
'unset' => colorSchemeDefault,
};
Replacing conditional ternary expressions
You can replace most conditional expressions cond ? x : y
with match expressions. This is particularly useful for complex or nested conditional expressions. For example:
1declare const obj:2 | {type: 'a', foo: number}3 | {type: 'b', bar: string}4 | null;5
6// Before7const a =8 obj === null9 ? 010 : obj.type === 'a'11 ? obj.foo12 : obj.bar.length;13
14// After15const b = match (obj) {16 {type: 'a', const foo} => foo,17 {type: 'b', const bar} => bar.length,18 null => {}19};
Dealing with disjoint object unions
Disjoint object unions are unions of object types with some distinguishing property. For example:
Previous patterns would involve checking the result.type
property, and then accessing properties off of result
:
1type Result = {type: 'ok', value: number} | {type: 'error', error: Error};2declare const result: Result;3
4switch (result.type) {5 case 'ok':6 console.log(result.value);7 break;8 case 'error':9 throw result.error;10}
With pattern matching you have to change how you think about it. Rather than doing the conditional checks on the type
property, you do it on the object itself, and destructure what you need right in the pattern:
1type Result = {type: 'ok', value: number} | {type: 'error', error: Error};2declare const result: Result;3
4match (result) {5 {type: 'ok', const value} => {6 console.log(value);7 }8 {type: 'error', const error} => {9 throw error;10 }11}
If you need to pass in the refined object type itself, you can use an as
pattern on the object pattern:
1type OK = {type: 'ok', value: number};2type Err = {type: 'error', error: Error}3type Result = OK | Err;4
5declare const result: Result;6
7match (result) {8 {type: 'ok', const value} => {9 console.log(value);10 }11 {type: 'error', ...} as err => {12 throw processError(err);13 }14}15
16declare function processError(err: Err): Error;