Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strict function types #79

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/coverage
/dist
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ module.exports = {
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-empty-function": "off"
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-non-null-assertion": "error"
}
};
123 changes: 70 additions & 53 deletions src/behavior.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { cons, DoubleLinkedList, Node, fromArray, nil } from "./datastructures";
import { combine, isPlaceholder } from "./index";
import { State, Reactive, Time, BListener, Parent, SListener } from "./common";
import { Future, BehaviorFuture } from "./future";
import { Cons, cons, DoubleLinkedList, fromArray, nil, Node } from "./datastructures";
import { __UNSAFE_GET_LAST_BEHAVIOR_VALUE, combine, isPlaceholder } from "./index";
import { BListener, Parent, Reactive, SListener, State, Time } from "./common";
import * as F from "./future";
import {
Stream,
FlatFuturesOrdered,
FlatFuturesLatest,
FlatFutures
} from "./stream";
import { tick, getTime } from "./clock";
import { sample, Now } from "./now";
import { BehaviorFuture, Future } from "./future";
import { FlatFutures, FlatFuturesLatest, FlatFuturesOrdered, Stream } from "./stream";
import { getTime, tick } from "./clock";
import { Now, sample } from "./now";

export type MapBehaviorTuple<A> = { [K in keyof A]: Behavior<A[K]> };

Expand All @@ -22,7 +17,7 @@ export type MapBehaviorTuple<A> = { [K in keyof A]: Behavior<A[K]> };
export abstract class Behavior<A> extends Reactive<A, BListener>
implements Parent<BListener> {
// Behaviors cache their last value in `last`.
last: A;
last?: A;
children: DoubleLinkedList<BListener> = new DoubleLinkedList();
pulledAt: number | undefined;
changedAt: number | undefined;
Expand Down Expand Up @@ -70,7 +65,7 @@ export abstract class Behavior<A> extends Reactive<A, BListener>
const time = t === undefined ? tick() : t;
this.pull(time);
}
return this.last;
return __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this);
}
abstract update(t: number): A;
pushB(t: number): void {
Expand All @@ -88,7 +83,11 @@ export abstract class Behavior<A> extends Reactive<A, BListener>
for (const parent of this.parents) {
if (isBehavior(parent)) {
parent.pull(t);
shouldRefresh = shouldRefresh || this.changedAt < parent.changedAt;
shouldRefresh =
shouldRefresh ||
(this.changedAt !== undefined &&
parent.changedAt !== undefined &&
this.changedAt < parent.changedAt);
}
}
if (shouldRefresh) {
Expand Down Expand Up @@ -122,7 +121,7 @@ export abstract class Behavior<A> extends Reactive<A, BListener>
}
}

export function pushToChildren(t: number, b: Behavior<unknown>): void {
export function pushToChildren<_>(t: number, b: Behavior<_>): void {
for (const child of b.children) {
child.pushB(t);
}
Expand All @@ -139,7 +138,7 @@ function refresh<A>(b: Behavior<A>, t: number) {

export function isBehavior<A>(b: unknown): b is Behavior<A> {
return (
(typeof b === "object" && "at" in b && !isPlaceholder(b)) ||
(typeof b === "object" && b !== null && "at" in b && !isPlaceholder(b)) ||
(isPlaceholder(b) && (b.source === undefined || isBehavior(b.source)))
);
}
Expand Down Expand Up @@ -184,14 +183,16 @@ class ProducerBehaviorFromFunction<A> extends ProducerBehavior<A> {
) {
super();
}
deactivateFn: () => void;
deactivateFn?: () => void;
activateProducer(): void {
this.state = State.Push;
this.deactivateFn = this.activateFn(this.newValue.bind(this));
}
deactivateProducer(): void {
this.state = State.Inactive;
this.deactivateFn();
if (this.deactivateFn !== undefined) {
this.deactivateFn();
}
}
}

Expand Down Expand Up @@ -243,17 +244,19 @@ export class MapBehavior<A, B> extends Behavior<B> {
this.parents = cons(parent);
}
update(_t: number): B {
return this.f(this.parent.last);
return this.f(__UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.parent));
}
}

class ApBehavior<A, B> extends Behavior<B> {
constructor(private fn: Behavior<(a: A) => B>, private val: Behavior<A>) {
super();
this.parents = cons<Behavior<((a: A) => B) | A>>(fn, cons(val));
this.parents = cons<Behavior<(a: A) => B> | Behavior<A>>(fn, cons(val));
}
update(_t: number): B {
return this.fn.last(this.val.last);
return __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.fn)(
__UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.val)
);
}
}

Expand Down Expand Up @@ -295,19 +298,22 @@ export class LiftBehavior<A extends unknown[], R> extends Behavior<R> {

class FlatMapBehavior<A, B> extends Behavior<B> {
// The last behavior returned by the chain function
private innerB: Behavior<B>;
private innerB?: Behavior<B> | undefined;
private innerNode: Node<this> = new Node(this);
constructor(private outer: Behavior<A>, private fn: (a: A) => Behavior<B>) {
super();
this.parents = cons(this.outer);
}
update(t: number): B {
const outerChanged = this.outer.changedAt > this.changedAt;
const outerChanged =
this.outer.changedAt !== undefined &&
this.changedAt !== undefined &&
this.outer.changedAt > this.changedAt;
if (outerChanged || this.changedAt === undefined) {
if (this.innerB !== undefined) {
this.innerB.removeListener(this.innerNode);
}
this.innerB = this.fn(this.outer.last);
this.innerB = this.fn(__UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.outer));
this.innerB.addListener(this.innerNode, t);
if (this.state !== this.innerB.state) {
this.changeStateDown(this.innerB.state);
Expand All @@ -317,28 +323,32 @@ class FlatMapBehavior<A, B> extends Behavior<B> {
this.innerB.pull(t);
}
}
return this.innerB.last;
if (this.innerB === undefined) {
// panic!
throw new Error("FlatMapBehavior#innerB should be defined");
}
return __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.innerB);
}
}

/** @private */
class WhenBehavior extends Behavior<Future<{}>> {
class WhenBehavior extends Behavior<Future<boolean>> {
constructor(private parent: Behavior<boolean>) {
super();
this.parents = cons(parent);
}
update(_t: number): Future<{}> {
update(_t: number): Future<boolean> {
return this.parent.last === true
? Future.of({})
? Future.of<boolean>(true)
: new BehaviorFuture(this.parent);
}
}

export function whenFrom(b: Behavior<boolean>): Behavior<Future<{}>> {
export function whenFrom(b: Behavior<boolean>): Behavior<Future<boolean>> {
return new WhenBehavior(b);
}

export function when(b: Behavior<boolean>): Now<Future<{}>> {
export function when(b: Behavior<boolean>): Now<Future<boolean>> {
return sample(whenFrom(b));
}

Expand All @@ -359,7 +369,7 @@ class SnapshotBehavior<A> extends Behavior<Future<A>> implements SListener<A> {
}
}
pushS(t: number, _val: A): void {
this.last.resolve(this.parent.at(t), t);
__UNSAFE_GET_LAST_BEHAVIOR_VALUE(this).resolve(this.parent.at(t), t);
this.parents = cons(this.parent);
this.changeStateDown(this.state);
this.parent.addListener(this.node, t);
Expand All @@ -368,7 +378,7 @@ class SnapshotBehavior<A> extends Behavior<Future<A>> implements SListener<A> {
if (this.future.state === State.Done) {
return Future.of(this.parent.at(t));
} else {
return this.last;
return __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this);
}
}
}
Expand Down Expand Up @@ -440,7 +450,7 @@ class SwitcherBehavior<A> extends ActiveBehavior<A>
next.addListener(this.nNode, t);
}
update(_t: Time): A {
return this.b.last;
return __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.b);
}
pushS(t: number, value: Behavior<A>): void {
this.doSwitch(t, value);
Expand Down Expand Up @@ -486,7 +496,9 @@ export function switcherFrom<A>(
init: Behavior<A>,
stream: Stream<Behavior<A>>
): Behavior<Behavior<A>> {
return fromFunction((t) => new SwitcherBehavior(init, stream, t));
return fromFunction<Behavior<A>>(
(t) => new SwitcherBehavior(init, stream, t)
);
}

export function switcher<A>(
Expand Down Expand Up @@ -636,10 +648,10 @@ export function stepper<B>(initial: B, steps: Stream<B>): Now<Behavior<B>> {
* @param turnOn the streams that turn the behavior on
* @param turnOff the streams that turn the behavior off
*/
export function toggleFrom(
export function toggleFrom<A, B>(
initial: boolean,
turnOn: Stream<unknown>,
turnOff: Stream<unknown>
turnOn: Stream<A>,
turnOff: Stream<B>
): Behavior<Behavior<boolean>> {
return stepperFrom(initial, turnOn.mapTo(true).combine(turnOff.mapTo(false)));
}
Expand All @@ -650,10 +662,10 @@ export function toggleFrom(
* @param turnOn the streams that turn the behavior on
* @param turnOff the streams that turn the behavior off
*/
export function toggle(
export function toggle<A, B>(
initial: boolean,
turnOn: Stream<unknown>,
turnOff: Stream<unknown>
turnOn: Stream<A>,
turnOff: Stream<B>
): Now<Behavior<boolean>> {
return sample(toggleFrom(initial, turnOn, turnOff));
}
Expand All @@ -662,7 +674,7 @@ export type SampleAt = <B>(b: Behavior<B>) => B;

class MomentBehavior<A> extends Behavior<A> {
private sampleBound: SampleAt;
private currentSampleTime: Time;
private currentSampleTime?: Time;
constructor(private f: (at: SampleAt) => A) {
super();
this.sampleBound = (b) => this.sample(b);
Expand Down Expand Up @@ -703,17 +715,18 @@ class MomentBehavior<A> extends Behavior<A> {
parent.removeListener(node);
}
}
this.parents = undefined;
const value = this.f(this.sampleBound);
return value;
this.parents = nil;
return this.f(this.sampleBound);
}
sample<B>(b: Behavior<B>): B {
const node = new Node(this);
this.listenerNodes = cons({ node, parent: b }, this.listenerNodes);
b.addListener(node, this.currentSampleTime);
b.at(this.currentSampleTime);
if (this.currentSampleTime !== undefined) {
b.addListener(node, this.currentSampleTime);
b.at(this.currentSampleTime);
}
this.parents = cons(b, this.parents);
return b.last;
return __UNSAFE_GET_LAST_BEHAVIOR_VALUE(b);
}
}

Expand All @@ -727,9 +740,9 @@ class FormatBehavior extends Behavior<string> {
private behaviors: Array<string | number | Behavior<string | number>>
) {
super();
let parents = undefined;
let parents: Cons<Behavior<string | number>> = nil;
for (const b of behaviors) {
if (isBehavior(b)) {
if (isBehavior<string | number>(b)) {
parents = cons(b, parents);
}
}
Expand All @@ -739,7 +752,9 @@ class FormatBehavior extends Behavior<string> {
let resultString = this.strings[0];
for (let i = 0; i < this.behaviors.length; ++i) {
const b = this.behaviors[i];
const value = isBehavior(b) ? b.last : b;
const value = isBehavior<string | number>(b)
? __UNSAFE_GET_LAST_BEHAVIOR_VALUE(b)
: b;
resultString += value.toString() + this.strings[i + 1];
}
return resultString;
Expand All @@ -763,7 +778,8 @@ export function flatFutures<A>(stream: Stream<Future<A>>): Now<Stream<A>> {

export const flatFuturesOrderedFrom = <A>(
stream: Stream<Future<A>>
): Behavior<Stream<A>> => fromFunction(() => new FlatFuturesOrdered(stream));
): Behavior<Stream<A>> =>
fromFunction<Stream<A>>(() => new FlatFuturesOrdered(stream));

export function flatFuturesOrdered<A>(
stream: Stream<Future<A>>
Expand All @@ -773,7 +789,8 @@ export function flatFuturesOrdered<A>(

export const flatFuturesLatestFrom = <A>(
stream: Stream<Future<A>>
): Behavior<Stream<A>> => fromFunction(() => new FlatFuturesLatest(stream));
): Behavior<Stream<A>> =>
fromFunction<Stream<A>>(() => new FlatFuturesLatest(stream));

export function flatFuturesLatest<A>(
stream: Stream<Future<A>>
Expand Down
Loading