From 906096a5cb5d60eb98e477fa8a56fc54030bb417 Mon Sep 17 00:00:00 2001 From: Matei Adriel <rafaeladriel11@gmail.com> Date: Sun, 22 Dec 2019 15:38:16 +0200 Subject: [PATCH] typescript(option): feat: Refactored everything to use nominal types typescript(option): typescript(option): BREAKING CHANGE: flat only works on one layer but is now strongly typed | everything uses nominal types now | match has the correct parameter order Signed-off-by: prescientmoon <git@moonythm.dev> --- typescript/option/src/helpers.ts | 76 ++++++++++------------- typescript/option/src/internalHelperts.ts | 1 - typescript/option/src/internals.ts | 28 ++------- typescript/option/src/types.ts | 18 ++++-- 4 files changed, 51 insertions(+), 72 deletions(-) diff --git a/typescript/option/src/helpers.ts b/typescript/option/src/helpers.ts index 296edd2..5f28819 100644 --- a/typescript/option/src/helpers.ts +++ b/typescript/option/src/helpers.ts @@ -7,48 +7,48 @@ import { BackFolder, Nullable } from './internalTypes' -import { always, identity } from './internalHelperts' -import Internals, { SomeClass, isOption } from './internals' +import { identity } from './internalHelperts' +import { none, some } from './internals' -export const isSome = <T>(option: Option<T>) => - option instanceof Internals.SomeClass -export const isNothing = <T>(option: Option<T>) => - option instanceof Internals.NoneClass +export const isSome = <T>(option: Option<T>): option is Some<T> => + option.type === some +export const isNothing = <T>(option: Option<T>): option is None => + option.type === none -export const match = <T, U>( - option: Option<T>, +const match = <T, U>( caseSome: Mapper<T, U>, - caseNone: Mapper<void, U> + _default: U, + option: Option<T> ) => { if (isSome(option)) { - return caseSome((option as SomeClass<T>)[Internals.someValue]) + return caseSome(option.value as T) } - return caseNone() + return _default } export const bind = <T, U>( binder: Binder<T, U>, option: Option<T> ): Option<U> => { - return match(option, binder, always(None)) + return match(binder, None, option) } export const map = <T, U>( mapper: Mapper<T, U>, option: Option<T> ): Option<U> => { - return match(option, v => Some(mapper(v)), always(None)) + return match(v => Some(mapper(v)), None, option) } export const count = <T>(option: Option<T>) => Number(isSome(option)) export const exists = <T>(predicate: Predicate<T>, option: Option<T>) => { - return match(option, predicate, always(false)) + return match(predicate, false, option) } export const filter = <T>(predicate: Predicate<T>, option: Option<T>) => { - return match(option, v => (predicate(v) ? Some(v) : None), always(None)) + return match(v => (predicate(v) ? Some(v) : None), None, option) } export const fold = <T, U>( @@ -56,7 +56,7 @@ export const fold = <T, U>( initial: U, option: Option<T> ) => { - return match(option, v => folder(initial, v), always(initial)) + return match(v => folder(initial, v), initial, option) } export const foldback = <T, U>( @@ -64,53 +64,41 @@ export const foldback = <T, U>( option: Option<T>, initial: U ) => { - return match(option, v => folder(v, initial), always(initial)) + return match(v => folder(v, initial), initial, option) } export const forall = <T>(predicate: Predicate<T>, option: Option<T>) => { - return match(option, predicate, () => true) + return match(predicate, true, option) } -export const get = <T>(option: Option<T>) => { - return match( - option, - v => v, - () => { - throw new Error('Cannot get value from None') - } - ) +export const get = <T>(option: Option<T>): T => { + if (isSome(option)) { + return option.value + } + + throw new Error(`Cannot get value of None`) } export const iter = <T>(mapper: Mapper<T, void>, option: Option<T>) => { - return match(option, mapper, always(None)) + if (isSome(option)) { + mapper(option.value) + } } export const toArray = <T>(option: Option<T>) => { - return match(option, v => [v], always([])) + return match(v => [v], [], option) } export const toNullable = <T>(option: Option<T>) => { - return match(option, identity, always(null)) + return match(identity, null, option) } export const withDefault = <T>(_default: T, option: Option<T>) => { - return match(option, identity, always(_default)) + return match(identity, _default, option) } -const checkIfOption = <T>(x): x is Option<T> => x[isOption] - -export const flat = <T, U>(option: Option<T>): Option<U> => { - return match( - option, - inner => { - if (checkIfOption(inner)) { - return flat(inner) - } else { - return Some(inner) - } - }, - always(None) - ) +export const flat = <T>(option: Option<Option<T>>): Option<T> => { + return bind(identity, option) } export const fromNullable = <T>(value: Nullable<T>): Option<T> => { diff --git a/typescript/option/src/internalHelperts.ts b/typescript/option/src/internalHelperts.ts index 693472b..d1600f3 100644 --- a/typescript/option/src/internalHelperts.ts +++ b/typescript/option/src/internalHelperts.ts @@ -1,2 +1 @@ -export const always = <T>(v: T) => () => v export const identity = <T>(v: T) => v diff --git a/typescript/option/src/internals.ts b/typescript/option/src/internals.ts index 2ec597a..f4845fd 100644 --- a/typescript/option/src/internals.ts +++ b/typescript/option/src/internals.ts @@ -1,25 +1,7 @@ -export const isOption = Symbol('option') -export const someValue = Symbol('value') +export const some = Symbol('some') +export const none = Symbol('none') -export class SomeClass<T> { - public [isOption] = true - public [someValue]: T - - public constructor(value: T) { - this[someValue] = value - } - - public toString() { - return `Some(${this[someValue]})` - } +export type NominalTyped<T, U> = { + type: T + value: U } - -export class NoneClass { - public [isOption] = true - - public toString() { - return 'None' - } -} - -export default { NoneClass, SomeClass, isOption, someValue } diff --git a/typescript/option/src/types.ts b/typescript/option/src/types.ts index 3f55148..ff1d556 100644 --- a/typescript/option/src/types.ts +++ b/typescript/option/src/types.ts @@ -1,6 +1,16 @@ -import * as Internals from './internals' +import { NominalTyped, none, some } from './internals' -export type Option<T> = Internals.SomeClass<T> | Internals.NoneClass +export type None = NominalTyped<typeof none, null> +export type Some<T> = NominalTyped<typeof some, T> -export const None = new Internals.NoneClass() -export const Some = <T>(v: T) => new Internals.SomeClass(v) +export type Option<T> = Some<T> | None + +export const None: Option<any> = { + type: none, + value: null +} + +export const Some = <T>(value: T): Option<T> => ({ + type: some, + value +})