1
Fork 0

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>
This commit is contained in:
Matei Adriel 2019-12-22 15:38:16 +02:00 committed by prescientmoon
parent db6417d871
commit 906096a5cb
Signed by: prescientmoon
SSH key fingerprint: SHA256:UUF9JT2s8Xfyv76b8ZuVL7XrmimH4o49p4b+iexbVH4
4 changed files with 51 additions and 72 deletions

View file

@ -7,48 +7,48 @@ import {
BackFolder, BackFolder,
Nullable Nullable
} from './internalTypes' } from './internalTypes'
import { always, identity } from './internalHelperts' import { identity } from './internalHelperts'
import Internals, { SomeClass, isOption } from './internals' import { none, some } from './internals'
export const isSome = <T>(option: Option<T>) => export const isSome = <T>(option: Option<T>): option is Some<T> =>
option instanceof Internals.SomeClass option.type === some
export const isNothing = <T>(option: Option<T>) => export const isNothing = <T>(option: Option<T>): option is None =>
option instanceof Internals.NoneClass option.type === none
export const match = <T, U>( const match = <T, U>(
option: Option<T>,
caseSome: Mapper<T, U>, caseSome: Mapper<T, U>,
caseNone: Mapper<void, U> _default: U,
option: Option<T>
) => { ) => {
if (isSome(option)) { 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>( export const bind = <T, U>(
binder: Binder<T, U>, binder: Binder<T, U>,
option: Option<T> option: Option<T>
): Option<U> => { ): Option<U> => {
return match(option, binder, always(None)) return match(binder, None, option)
} }
export const map = <T, U>( export const map = <T, U>(
mapper: Mapper<T, U>, mapper: Mapper<T, U>,
option: Option<T> option: Option<T>
): Option<U> => { ): 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 count = <T>(option: Option<T>) => Number(isSome(option))
export const exists = <T>(predicate: Predicate<T>, option: Option<T>) => { 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>) => { 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>( export const fold = <T, U>(
@ -56,7 +56,7 @@ export const fold = <T, U>(
initial: U, initial: U,
option: Option<T> 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>( export const foldback = <T, U>(
@ -64,53 +64,41 @@ export const foldback = <T, U>(
option: Option<T>, option: Option<T>,
initial: U 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>) => { 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>) => { export const get = <T>(option: Option<T>): T => {
return match( if (isSome(option)) {
option, return option.value
v => v,
() => {
throw new Error('Cannot get value from None')
} }
)
throw new Error(`Cannot get value of None`)
} }
export const iter = <T>(mapper: Mapper<T, void>, option: Option<T>) => { 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>) => { 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>) => { 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>) => { 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>(option: Option<Option<T>>): Option<T> => {
return bind(identity, option)
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 fromNullable = <T>(value: Nullable<T>): Option<T> => { export const fromNullable = <T>(value: Nullable<T>): Option<T> => {

View file

@ -1,2 +1 @@
export const always = <T>(v: T) => () => v
export const identity = <T>(v: T) => v export const identity = <T>(v: T) => v

View file

@ -1,25 +1,7 @@
export const isOption = Symbol('option') export const some = Symbol('some')
export const someValue = Symbol('value') export const none = Symbol('none')
export class SomeClass<T> { export type NominalTyped<T, U> = {
public [isOption] = true type: T
public [someValue]: T value: U
public constructor(value: T) {
this[someValue] = value
}
public toString() {
return `Some(${this[someValue]})`
}
} }
export class NoneClass {
public [isOption] = true
public toString() {
return 'None'
}
}
export default { NoneClass, SomeClass, isOption, someValue }

View file

@ -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 type Option<T> = Some<T> | None
export const Some = <T>(v: T) => new Internals.SomeClass(v)
export const None: Option<any> = {
type: none,
value: null
}
export const Some = <T>(value: T): Option<T> => ({
type: some,
value
})