Skip to content

Instantly share code, notes, and snippets.

@beardedtim
Last active March 3, 2022 19:13
Show Gist options
  • Save beardedtim/03d8f912d4e4441c0f728f2f004826f7 to your computer and use it in GitHub Desktop.
Save beardedtim/03d8f912d4e4441c0f728f2f004826f7 to your computer and use it in GitHub Desktop.
Fantasy Land Value
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