Implement Option
This commit is contained in:
@@ -1 +1,2 @@
|
||||
export * as result from './src/result'
|
||||
export * as option from './src/option'
|
||||
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
import logger from 'log'
|
||||
import {Result} from './result'
|
||||
|
||||
const log = logger('option')
|
||||
|
||||
export class Option<T> {
|
||||
// *****************************************************************************************************************
|
||||
// Constructors
|
||||
private constructor(
|
||||
private readonly data: T | null,
|
||||
private readonly _state: State,
|
||||
) {}
|
||||
public static some<T>(data: T) : Option<T> {
|
||||
return new Option(data, State.Some)
|
||||
}
|
||||
public static none<T>() : Option<T> {
|
||||
return new Option<T>(null, State.None)
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************
|
||||
// some
|
||||
public is_some() : boolean {
|
||||
return this._state === State.Some
|
||||
}
|
||||
public is_some_and(f: (data: T) => boolean) : boolean {
|
||||
return this._state === State.Some && f(this.data as T)
|
||||
}
|
||||
public expect(msg: string) : T {
|
||||
log.trace("expect")
|
||||
|
||||
if (this._state === State.Some) {
|
||||
return this.data as T
|
||||
}
|
||||
|
||||
log.warn(msg)
|
||||
throw new Error(msg)
|
||||
}
|
||||
public unwrap() : T {
|
||||
log.trace("unwrap")
|
||||
return this.expect("called unwrap on a None value")
|
||||
}
|
||||
public unwrap_or(default_: T) : T {
|
||||
if (this._state === State.Some) return this.data as T
|
||||
return default_
|
||||
}
|
||||
public unwrap_or_else(f: () => T) : T {
|
||||
if (this._state === State.Some) return this.data as T
|
||||
return f()
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************
|
||||
// none
|
||||
public is_none() : boolean {
|
||||
return this._state === State.None
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************
|
||||
// map
|
||||
public map<U>(f: (data: T) => U) : Option<U> {
|
||||
if (this._state === State.Some) return Option.some(f(this.data as T))
|
||||
return Option.none()
|
||||
}
|
||||
public map_or<U>(default_: U, f: (data: T) => U) : U {
|
||||
if (this._state === State.Some) return f(this.data as T)
|
||||
return default_
|
||||
}
|
||||
public map_or_else<U>(default_: () => U, f: (data: T) => U) : U {
|
||||
if (this._state === State.Some) return f(this.data as T)
|
||||
return default_()
|
||||
}
|
||||
public ok_or<E>(err: E) : Result<T, E> {
|
||||
if (this._state === State.Some) return Result.ok(this.data as T)
|
||||
return Result.error(err)
|
||||
}
|
||||
public ok_or_else<E>(err: () => E) : Result<T, E> {
|
||||
if (this._state === State.Some) return Result.ok(this.data as T)
|
||||
return Result.error(err())
|
||||
}
|
||||
public and<U>(optb: Option<U>) : Option<U> {
|
||||
if (this._state === State.Some) return optb
|
||||
return Option.none()
|
||||
}
|
||||
public and_then<U>(f: (data: T) => Option<U>) : Option<U> {
|
||||
if (this._state === State.Some) return f(this.data as T)
|
||||
return Option.none()
|
||||
}
|
||||
public filter(f: (data: T) => boolean) : Option<T> {
|
||||
if (this._state === State.Some && f(this.data as T)) return this
|
||||
return Option.none()
|
||||
}
|
||||
public or(optb: Option<T>) : Option<T> {
|
||||
if (this._state === State.Some) return this
|
||||
return optb
|
||||
}
|
||||
public or_else(f: () => Option<T>) : Option<T> {
|
||||
if (this._state === State.Some) return this
|
||||
return f()
|
||||
}
|
||||
public xor(optb: Option<T>) : Option<T> {
|
||||
if (this._state === optb._state) return Option.none()
|
||||
else if (this._state === State.Some) return this
|
||||
return optb
|
||||
}
|
||||
|
||||
public get state() : State {
|
||||
return this._state
|
||||
}
|
||||
}
|
||||
|
||||
export enum State {
|
||||
Some,
|
||||
None,
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import {expect, test} from 'bun:test'
|
||||
|
||||
import {Option, State} from 'option'
|
||||
|
||||
test('Base case Some', () => {
|
||||
const value = 12
|
||||
const opt = Option.some(value)
|
||||
|
||||
const value2 = 1
|
||||
const opt2 = Option.some(value2)
|
||||
|
||||
const handler_throw = () => {
|
||||
throw new Error("Shouldn't happen")
|
||||
}
|
||||
|
||||
expect(opt.is_some()).toBeTrue()
|
||||
expect(opt.is_some_and(data => data === value)).toBeTrue()
|
||||
expect(opt.expect("Shouldn't happen")).toBe(value)
|
||||
expect(opt.unwrap()).toBe(value)
|
||||
expect(opt.unwrap_or(0)).toBe(value)
|
||||
expect(opt.unwrap_or_else(handler_throw)).toBe(value)
|
||||
expect(opt.is_none()).toBeFalse()
|
||||
expect(opt.map(data => data + 1).unwrap()).toBe(value + 1)
|
||||
expect(opt.map_or(0, data => data + 1)).toBe(value + 1)
|
||||
expect(opt.map_or_else(handler_throw, data => data + 1)).toBe(value + 1)
|
||||
expect(opt.ok_or("Shouldn't happen").unwrap()).toBe(value)
|
||||
expect(opt.ok_or_else(handler_throw).unwrap()).toBe(value)
|
||||
expect(opt.and(opt2).unwrap()).toBe(value2)
|
||||
expect(opt.and_then(data => Option.some(data + 1)).unwrap()).toBe(value + 1)
|
||||
expect(opt.filter(data => data === value).unwrap()).toBe(value)
|
||||
expect(opt.or(opt2).unwrap()).toBe(value)
|
||||
expect(opt.or_else(handler_throw).unwrap()).toBe(value)
|
||||
|
||||
expect(opt.xor(Option.none()).unwrap()).toBe(value)
|
||||
expect(opt.xor(opt2).is_none()).toBeTrue()
|
||||
|
||||
expect(opt.state).toBe(State.Some)
|
||||
})
|
||||
test('Base case None', () => {
|
||||
const opt = Option.none<number>()
|
||||
const value2 = 12
|
||||
const opt2 = Option.some(value2)
|
||||
|
||||
const handler_throw = () => {
|
||||
throw new Error("Shouldn't happen")
|
||||
}
|
||||
|
||||
expect(opt.is_some()).toBeFalse()
|
||||
expect(opt.is_some_and(handler_throw)).toBeFalse()
|
||||
expect(() => opt.expect("Shouldn't happen")).toThrow()
|
||||
expect(() => opt.unwrap()).toThrow()
|
||||
expect(opt.unwrap_or(0)).toBe(0)
|
||||
expect(opt.unwrap_or_else(() => 0)).toBe(0)
|
||||
expect(opt.is_none()).toBeTrue()
|
||||
expect(opt.map(handler_throw).is_none()).toBeTrue()
|
||||
expect(opt.map_or(0, handler_throw)).toBe(0)
|
||||
expect(opt.map_or_else(() => 0, handler_throw)).toBe(0)
|
||||
expect(opt.ok_or(0).unwrap_err()).toBe(0)
|
||||
expect(opt.ok_or_else(() => 0).unwrap_err()).toBe(0)
|
||||
expect(opt.and(opt2).is_none()).toBeTrue()
|
||||
expect(opt.and_then(handler_throw).is_none()).toBeTrue()
|
||||
expect(opt.filter(handler_throw).is_none()).toBeTrue()
|
||||
expect(opt.or(opt2).unwrap()).toBe(value2)
|
||||
expect(opt.or_else(() => opt2).unwrap()).toBe(value2)
|
||||
|
||||
expect(opt.xor(Option.none()).is_none()).toBeTrue()
|
||||
expect(opt.xor(opt2).unwrap()).toBe(value2)
|
||||
|
||||
expect(opt.state).toBe(State.None)
|
||||
})
|
||||
Reference in New Issue
Block a user