Last active
March 3, 2022 19:13
-
-
Save beardedtim/03d8f912d4e4441c0f728f2f004826f7 to your computer and use it in GitHub Desktop.
Fantasy Land Value
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import EE from 'events' | |
export const EVENTS = { | |
NEW_VALUE: 'new value', | |
} | |
/** | |
* A Value is a way to represent a single, atomic _primitve_ | |
* that we want to interact with. It can be observed over time | |
* and can be used to make things such as Records/Maps and | |
* Lists/Sets. | |
* | |
* Implements (https://github.com/fantasyland/fantasy-land) | |
* | |
* - Functor | |
* - Apply | |
* - Applicative | |
* - Chain | |
* - Extend | |
* - Monad | |
* | |
* You can access any of those methods with or without the | |
* fantasy-land/ prefix. Example: | |
* | |
* const value = new Value(1) | |
* | |
* value.map(num => num * 2) | |
* .equals( | |
* value['fantasy-land/map'](num => num *2) | |
* ) | |
*/ | |
export class Value<T = unknown> extends EE { | |
#currentValue: T | |
constructor(value: T) { | |
super() | |
this.#currentValue = value | |
} | |
static 'fantasy-land/of'<T>(value: T) { | |
return Value.of(value) | |
} | |
static of<T>(value: T) { | |
return new Value(value) | |
} | |
static lift<T = unknown>(value: Value<T>) { | |
return value.value | |
} | |
get value() { | |
return this.#currentValue | |
} | |
set value(value: T) { | |
this.#changed(value) | |
this.#currentValue = value | |
} | |
#changed(newValue: T) { | |
this.emit(EVENTS.NEW_VALUE, newValue, Value.lift(this)) | |
} | |
map<U = unknown>(transformer: (v: T) => U): Value<U> { | |
const value = Value.of(transformer(Value.lift(this))) | |
this.on(EVENTS.NEW_VALUE, (newValue) => { | |
value.value = transformer(newValue) | |
}) | |
return value | |
} | |
'fantasy-land/map'<U = unknown>(transformer: (v: T) => U) { | |
return this.map(transformer) | |
} | |
ap<U = unknown>(transformer: (v: T) => U): U { | |
return transformer(Value.lift(this)) | |
} | |
'fantasy-land/ap'<U = unknown>(transformer: (v: T) => U) { | |
return this.ap(transformer) | |
} | |
chain<U = unknown>(transformer: (v: T) => Value<U>): Value<U> { | |
const value = transformer(Value.lift(this)) | |
return Value.of(value.value) | |
} | |
'fantasy-land/chain'<U = unknown>(transformer: (v: T) => Value<U>) { | |
return this.chain(transformer) | |
} | |
extend<U = unknown>(transformer: (v: Value<T>) => U) { | |
return Value.of(transformer(this)) | |
} | |
'fantasy-land/extend'<U = unknown>(transformer: (v: Value<T>) => U) { | |
return this.extend(transformer) | |
} | |
equals(value: Value<unknown>) { | |
return Value.lift(this) === Value.lift(value) | |
} | |
'fantasy-land/equals'(value: Value<unknown>) { | |
return this.equals(value) | |
} | |
/** | |
* Subscribe to the changes of this value over time | |
* | |
* Returns a function that will allow you to unsubscribe | |
* for changes | |
*/ | |
subscribe(cb: (value: T) => unknown) { | |
this.on(EVENTS.NEW_VALUE, cb) | |
return () => this.removeListener(EVENTS.NEW_VALUE, cb) | |
} | |
} | |
export default Value |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment