Skip to content

Instantly share code, notes, and snippets.

@alexsasharegan
Created August 15, 2018 20:08
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexsasharegan/1a681e8b79d731407eb67a74eab66937 to your computer and use it in GitHub Desktop.
Save alexsasharegan/1a681e8b79d731407eb67a74eab66937 to your computer and use it in GitHub Desktop.
A guide to pseudo pattern matching in TypeScript.
/**
* Step 1:
* Create a string enum. This can be done with a TS enum type,
* or a sum type (union of strings).
*/
// enum style
enum TokenType {
Text = "Text",
UserMention = "UserMention",
ChannelMention = "ChannelMention",
}
// sum type style
type TokenType2 = "Text" | "UserMention" | "ChannelMention";
/**
* Step 2:
* Define an object type that uses a discriminant (a common key),
* the type of which is your enum.
*/
interface Token {
type: TokenType;
value: string;
}
/**
* Step 3:
* Create a matcher type.
* This will the object that behaves like our pseudo pattern matcher.
*/
type TokenMatcher<T> = { [K in TokenType]: (value: string) => T };
/**
* Step 4:
* Implement the match.
* We need our type with the discriminant from step 2
* and the matcher from step 3 to do this.
* We also need a helper func, `expectNever`.
* The helper will guarantee we handle the enum exhaustively,
* or the compiler will error. This is accomplished with the `never` type.
* See the helper func for more info.
*/
function matchTokenType<T>(tkn: Token, matcher: TokenMatcher<T>): T {
let { type, value } = tkn;
switch (type) {
case TokenType.Text:
return matcher.Text(value);
case TokenType.UserMention:
return matcher.UserMention(value);
case TokenType.ChannelMention:
return matcher.ChannelMention(value);
default:
return expectNever(type);
}
}
/**
* `never` is a type that can't exist. If we handle every possible case in our
* enum above, the default case will never match.
*
* An example of `never` would be the type of a variable assigned directly after
* throwing an error. Since the error is thrown, the assignment will never run.
* This type allows TS to signal things like unreachable code.
*/
function expectNever(_: never, message = "Non exhaustive match."): never {
throw new Error(message);
}
/********************************** EXAMPLE ***********************************/
let parsedSlackMessage: Token[] = [
{ type: TokenType.Text, value: "Hello " },
{ type: TokenType.UserMention, value: "John" },
{ type: TokenType.Text, value: "! Welcome to the channel " },
{ type: TokenType.ChannelMention, value: "general" },
{ type: TokenType.Text, value: ". " },
{ type: TokenType.UserMention, value: "channel" },
{ type: TokenType.Text, value: ", say hello to our newest member!" },
];
// Convert the tokenized message to a string:
let stringifiedMessage = parsedSlackMessage.reduce(
(msg, tkn) =>
msg +
matchTokenType(tkn, {
Text: s => s,
ChannelMention: s => "#" + s,
UserMention: s => "@" + s,
}),
""
);
console.log(stringifiedMessage);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment