From 906096a5cb5d60eb98e477fa8a56fc54030bb417 Mon Sep 17 00:00:00 2001 From: Matei Adriel 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 --- 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 = (option: Option) => - option instanceof Internals.SomeClass -export const isNothing = (option: Option) => - option instanceof Internals.NoneClass +export const isSome = (option: Option): option is Some => + option.type === some +export const isNothing = (option: Option): option is None => + option.type === none -export const match = ( - option: Option, +const match = ( caseSome: Mapper, - caseNone: Mapper + _default: U, + option: Option ) => { if (isSome(option)) { - return caseSome((option as SomeClass)[Internals.someValue]) + return caseSome(option.value as T) } - return caseNone() + return _default } export const bind = ( binder: Binder, option: Option ): Option => { - return match(option, binder, always(None)) + return match(binder, None, option) } export const map = ( mapper: Mapper, option: Option ): Option => { - return match(option, v => Some(mapper(v)), always(None)) + return match(v => Some(mapper(v)), None, option) } export const count = (option: Option) => Number(isSome(option)) export const exists = (predicate: Predicate, option: Option) => { - return match(option, predicate, always(false)) + return match(predicate, false, option) } export const filter = (predicate: Predicate, option: Option) => { - return match(option, v => (predicate(v) ? Some(v) : None), always(None)) + return match(v => (predicate(v) ? Some(v) : None), None, option) } export const fold = ( @@ -56,7 +56,7 @@ export const fold = ( initial: U, option: Option ) => { - return match(option, v => folder(initial, v), always(initial)) + return match(v => folder(initial, v), initial, option) } export const foldback = ( @@ -64,53 +64,41 @@ export const foldback = ( option: Option, initial: U ) => { - return match(option, v => folder(v, initial), always(initial)) + return match(v => folder(v, initial), initial, option) } export const forall = (predicate: Predicate, option: Option) => { - return match(option, predicate, () => true) + return match(predicate, true, option) } -export const get = (option: Option) => { - return match( - option, - v => v, - () => { - throw new Error('Cannot get value from None') - } - ) +export const get = (option: Option): T => { + if (isSome(option)) { + return option.value + } + + throw new Error(`Cannot get value of None`) } export const iter = (mapper: Mapper, option: Option) => { - return match(option, mapper, always(None)) + if (isSome(option)) { + mapper(option.value) + } } export const toArray = (option: Option) => { - return match(option, v => [v], always([])) + return match(v => [v], [], option) } export const toNullable = (option: Option) => { - return match(option, identity, always(null)) + return match(identity, null, option) } export const withDefault = (_default: T, option: Option) => { - return match(option, identity, always(_default)) + return match(identity, _default, option) } -const checkIfOption = (x): x is Option => x[isOption] - -export const flat = (option: Option): Option => { - return match( - option, - inner => { - if (checkIfOption(inner)) { - return flat(inner) - } else { - return Some(inner) - } - }, - always(None) - ) +export const flat = (option: Option>): Option => { + return bind(identity, option) } export const fromNullable = (value: Nullable): Option => { 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 = (v: T) => () => v export const identity = (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 { - public [isOption] = true - public [someValue]: T - - public constructor(value: T) { - this[someValue] = value - } - - public toString() { - return `Some(${this[someValue]})` - } +export type NominalTyped = { + 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 = Internals.SomeClass | Internals.NoneClass +export type None = NominalTyped +export type Some = NominalTyped -export const None = new Internals.NoneClass() -export const Some = (v: T) => new Internals.SomeClass(v) +export type Option = Some | None + +export const None: Option = { + type: none, + value: null +} + +export const Some = (value: T): Option => ({ + type: some, + value +})