Skip to content

Instantly share code, notes, and snippets.

@hugohabicht01
Created March 2, 2024 18:50
Show Gist options
  • Save hugohabicht01/f373f9f83bc10dd70bf7c356d2be8fa7 to your computer and use it in GitHub Desktop.
Save hugohabicht01/f373f9f83bc10dd70bf7c356d2be8fa7 to your computer and use it in GitHub Desktop.
/**
* Rust like option type in typescript, credit goes to Dillon Mulroy who originally posted this here:
* https://twitter.com/dillon_mulroy/status/1729648443457405385
* and here:
* https://www.typescriptlang.org/play#code/KYDwDg9gTgLgBAYwgOwM7wMoHkCyBRALjgFdkBLAR2ODlQE8BbAIwgBs4BeODRl1gCgBE2fIICUAbgBQoSLEQp0cAHJZlhEuSo16zNp2682Q1evHSZ4aPBh0wNDBAbAAagENW1ADwAVAHwGPnCgMMDIACaocMjErOwAPprhwABmZMjA4XCJyig0APzRwABuwFBwRD7SstZwtvZwWGAwZCi+AVy5GdncTsDtFvUOfV4Agh1wAErAbuEorHReAN5ScHAA1unhREMQKdy4eNJrxR7URI7O7p7948dwUMAwxFDI-Kc3F33X3uNiX84xn57gw3GAvAAhPz8FLIIjvM7AAGuRFAsScAKXFE3SF+f69QFQ+5uCI+AAWYVxMLhcARnwJ2N+eIxjWarWQuPxTRabSJq00AHcoGD+PjRiC3DAEGSqaCpWSiCs1mtUH14R9zgyfrdmRxMd9UXzldE8vCAPpEDKlKDovVwCH3AC++Kx2tx0kdwKkUiGKjyBmms3miyVGy2OzswD2KjUR35j2er1FRC6wAl4Kh1PNlpKZVtAQh+NTxNJFI5mdh2aK1vzrJ55bxKby91IQpF+KtZQl8qpYEloVeitoatpFureZZDpNGSrnZtk7gzqIDqknosSDQmD6BlDCZeHPGdM1rtRf2RQLgobWe9el-5xs2EQuhwANPflRrgG-jdenvvkQAdDeyDfj+oJgNS6JXj+wEMkBf5JrCR7AGIkjvmsjqgcaJLhOSYSQXeP7KrBSGfmhRGYehgrChBUFUb+ibIHAn73MalFEXK0r8JxZJ0URDH7nAPEAaqzjIeRP7scqjpOm+MnehuSipju8YIW8Rb+tBinwKgwCsPsXDQWsj7bDG6hYcRamioR-Gwbp+msdJFlrOB1lGZZjG0HpKSORhzlwDheHqTZRF2d5vmLv5rY0W59F1GSUAQAKRTJXgUCJVAQgAMIksgEDwNFYJ+hk5hUVJxo8bKkrSkOolIqOOY1pOEjTvV1l2hCi58bZalCdVZIAXlGSihF5WLgFUTaRYxphQ5-KUfJUjaXW7IqSqI5YvBjGgUN9Wplt+6gWQqBYkC-AQGyKBENy7JokQF31nAx0Mhe0GwQ97IASZnAcFwIhxn5-LHamZ0fVdK1tGecBg0xz3KW9vUw19Ww-Z0sayfyhXgoeMPXZdB6NnAowhQ8iP4wBWMjfNckWAA9LTcA+JG0P7AABnV2qs09UQ3W0MTMGUfhLYoOkGjcBi88gIl9PwACMYj8gBrniSy-A4JKA3ChETjWX4XAAAwAQArHAhSfnAADUcCyxUzGInAAC01uoYrgVlvwKt2shcB69bpsQ1LdUq3j9aDXkoqoXTDNMw00aszxmSc9zcDszAUDpAA5qzwubn18qJ-bXAc4iSv9fwoZB2RJOwazEAvHb4vHUQAAkSyfo6rMY2su2xTNvWCHlDfUIIGPOlH9o0AgbiPCksQvvFz1T0xMAJUlAVMWUGUAIRSPTcAAJJs8XNxc89dflKzWJc6c6ckjAb57yvNC+nHWP2OEScCmQcRwEwNCs-zP+UBs7LTfgXcWRcxbUApsgNstFx772QB8MgWQYYKE3FAYgCAYDQCiCvSUiA3DEF0nUZmm9cE5yUOkZB4RJYS3JkHGIcQ0LLWoR4FBdCuCS2lmJUgyQ0gZHCCwkWT0kHsNofjehocg7cJ7pHKQQA
*/
export const SOME: unique symbol = Symbol("SOME");
export const NONE: unique symbol = Symbol("NONE");
export type SomeValue<T> = T extends null | undefined | None ? never : T;
export type Option<T> = None | Some<T>;
type Some<A> = Readonly<{
kind: typeof SOME;
value: SomeValue<A>;
return(value: SomeValue<A>): Some<A>;
map<B>(fn: (value: SomeValue<A>) => SomeValue<B>): Some<B>;
andThen<B>(fn: (value: SomeValue<A>) => Option<B>): Option<B>;
unwrap(): A;
match<B>(match: {
some: (value: SomeValue<A>) => SomeValue<B>;
none: (_: never) => B;
}): SomeValue<B>;
}>;
type None = Readonly<{
kind: typeof NONE;
return(): None;
map<B>(fn: (_: never) => B): None;
andThen<B>(fn: (_: never) => Option<B>): None;
unwrap(): never;
match<B>(pattern: { some: (_: never) => B; none: (_: never) => B }): B;
}>;
const Some = {
return<A>(value: SomeValue<A>): Some<A> {
return {
kind: SOME,
value,
return: Some.return,
map(fn) {
return Some.return(fn(value));
},
andThen(fn) {
return fn(value);
},
unwrap() {
return value;
},
match(match) {
return match.some(value);
},
};
},
};
const None = {
return(): None {
const self = {
kind: NONE,
return() {
return self;
},
map() {
return self;
},
andThen() {
return self;
},
unwrap() {
throw new Error("Cannot unwrap None");
},
match<B>(match: { some: (_: never) => B; none: () => B }) {
return match.none();
},
} as const;
return self;
},
};
const Option = {
some: Some.return,
none: None.return,
isSome<A>(option: Option<A>): option is Some<A> {
return option.kind === SOME;
},
isNone<A>(option: Option<A>): option is None {
return option.kind === NONE;
},
unwrap<A>(option: Option<A>): A {
return option.unwrap();
},
};
// Type of `someValue` is Option<number>
const someValue = Option.some(1)
.map((value) => (Math.random() >= 0.5 ? value + 1 : value - 1))
.andThen((value) => (value >= 1 ? Option.some(value) : Option.none()));
// Type of `matchedValue` is `string`
const matchedValue = someValue.match({
some(value) {
return `our value is: ${value}`;
},
none() {
return "no value";
},
});
// Be careful, this can throw an error!
// If `someValue` is our `Some` variant,
// the type of `unwrappedValue` will be `number`
const unwrappedValue = someValue.unwrap();
// Invalid option constructors that cause type errors
const invalidOption = Option.some(null);
const invalidOption = Option.some(undefined);
const invalidOption = Option.some(Option.none());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment