Skip to content

Commit

Permalink
Implement stateful naming convention (#61)
Browse files Browse the repository at this point in the history
* rename stateful operators to new convention

* add sampled stateful operators and fix feedback

* add missing sampled stateful operators

* feedback fix

* tests use sampled version
  • Loading branch information
limemloh authored and paldepind committed Feb 26, 2019
1 parent 9ee6a07 commit f5ac041
Show file tree
Hide file tree
Showing 15 changed files with 292 additions and 169 deletions.
87 changes: 44 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ performant.

## Key features

* Simple and precise semantics. This means that everything in the
- Simple and precise semantics. This means that everything in the
library can be understood based on a very simple mental model. This
makes the library easy to use and free from surprises.
* Purely functional API.
* Based on classic FRP. This means that the library makes a
- Purely functional API.
- Based on classic FRP. This means that the library makes a
distinction between behaviors and streams.
* Supports continuous time for expressive and efficient creation of
- Supports continuous time for expressive and efficient creation of
time-dependent behavior.
* Integrates with declarative side-effects in a way that is pure,
- Integrates with declarative side-effects in a way that is pure,
testable and uses FRP for powerful handling of asynchronous
operations.
* Declarative testing. Hareactive programs are easy to test
- Declarative testing. Hareactive programs are easy to test
synchronously and declaratively.
* Great performance.
- Great performance.

## Introduction

Expand Down Expand Up @@ -54,12 +54,12 @@ time.

## Table of contents

* [Installation](#installation)
* [Conceptual overview](#conceptual-overview)
* [Tutorial/cookbook](#tutorial-cookbook)
* [API documentation](#api)
* [Contributing](#contributing)
* [Benchmark](#benchmark)
- [Installation](#installation)
- [Conceptual overview](#conceptual-overview)
- [Tutorial/cookbook](#tutorial-cookbook)
- [API documentation](#api)
- [Contributing](#contributing)
- [Benchmark](#benchmark)

# Installation

Expand Down Expand Up @@ -187,17 +187,17 @@ a behavior:

Below are some examples:

* The time remaining before an alarm goes off: The remaining time
- The time remaining before an alarm goes off: The remaining time
always have a current value, therefore it is a behavior.
* The moment where the alarm goes off: This has no current value. And
- The moment where the alarm goes off: This has no current value. And
since the alarm only goes off a single time this is a future.
* User clicking on a specific button: This has no notion of a current
- User clicking on a specific button: This has no notion of a current
value. And the user may press the button more than once. Thus a
stream is the proper representation.
* Whether or not a button is currently pressed: This always has a
- Whether or not a button is currently pressed: This always has a
current value. The button is always either pressed or not pressed.
This should be represented as a behavior.
* The tenth time a button is pressed: This happens once at a specific
- The tenth time a button is pressed: This happens once at a specific
moment in time. Use a future.

### Now
Expand All @@ -210,10 +210,10 @@ A value of type `Now` is a _description_ of something that we'd like
to do. Such a description can declare that it wants to do one of two
things.

* Get the current value of behavior. This is done with the `sample`
- Get the current value of behavior. This is done with the `sample`
function. Since a `Now`-computation will always be run in the
present it is impossible to sample a behavior in the past.
* Describe side-effects. This is done with functions such as `perform`
- Describe side-effects. This is done with functions such as `perform`
and `performStream`. With these functions we can describe things
that should happen when a stream occurs.

Expand All @@ -235,21 +235,21 @@ A notorious problem in FRP is how to implement functions that return
behaviors or streams that depend on the past. Such behaviors or
streams are called "stateful"

For instance `scan` creates a behavior that accumulates values over
For instance `accumFrom` creates a behavior that accumulates values over
time. Clearly such a behavior depends on the past. Thus we say that
`scan` returns a stateful behavior.
`accumFrom` returns a stateful behavior.

Implementing stateful methods such as `scan` in a way that is both
Implementing stateful methods such as `accumFrom` in a way that is both
intuitive to use, pure and memory safe is very tricky.

When implementing functions such as `scan` most reactive libraries in
When implementing functions such as `accumFrom` most reactive libraries in
JavaScript do one of these two things:

* Calling `scan` doesn't begin accumulating state at all. Only when
someone starts observing the result of `scan` is state accumulated.
- Calling `accumFrom` doesn't begin accumulating state at all. Only when
someone starts observing the result of `accumFrom` is state accumulated.
This is very counter intuitive behavior.
* Calling `scan` starts accumulating state from when `scan` is called.
This is pretty easy to understand. But it makes `scan` impure as it
- Calling `accumFrom` starts accumulating state from when `accumFrom` is called.
This is pretty easy to understand. But it makes `accumFrom` impure as it
will not return the same behavior when called at different time.

To solve this problem Hareactive uses a solution invented by Atze van
Expand All @@ -260,21 +260,21 @@ behavior and purity.
The solution means that some functions return a value that, compared
to what one might expect, is wrapped in an "extra" behavior. This
"behavior wrapping" is applied to all functions that return a result
that depends on the past. The before mentioned `scan`, for instance,
that depends on the past. The before mentioned `accumFrom`, for instance,
returns a value of type `Behavior<Behavior<A>>`.

Remember that a behavior is a value that depends on time. It is a
function from time. Therefore a behavior of a behavior is like a value
that depends on _two_ moments in time. This makes sense for `scan`
that depends on _two_ moments in time. This makes sense for `accumFrom`
because the result of accumulating depends both on when we _start_
accumulating and where we are now.

To get rid of the extra layer of nesting we often use `sample`. The
`sample` function returns a `Now`-computation that asks for the
current value of a behavior. It has the type `(b: Behavior<A>) => Now<A>`. Using `sample` with `scan` looks like this.
current value of a behavior. It has the type `(b: Behavior<A>) => Now<A>`. Using `sample` with `accumFrom` looks like this.

```js
const count = sample(scan((acc, inc) => acc + inc, 0, incrementStream));
const count = sample(accumFrom((acc, inc) => acc + inc, 0, incrementStream));
```

Here `count` has type `Now<Behavior<A>>` and it represents a
Expand Down Expand Up @@ -305,8 +305,8 @@ this. The table below gives an overview.
| Behavior | anything | `sample` (when inside Now) |
| Behavior | Behavior | `flatten` |
| Behavior | Stream | `switchStream` |
| Stream | Behavior | `switcher`, `selfie` |
| Stream | Stream | `switchStreamS` |
| Stream | Behavior | `switcherFrom`, `selfie` |
| Stream | Stream | `switchStreamFrom` |
| Stream | Future | n/a |
| Future | Behavior | `switchTo` |

Expand Down Expand Up @@ -387,10 +387,10 @@ lift((n, m, q) => (n + m) / q, behaviorN, behaviorM, behaviorQ);

#### How do I turn a stream into a behavior?

You probably want `stepper`:
You probably want `stepperFrom`:

```js
const b = stepper(initial, stream);
const b = stepperFrom(initial, stream);
```

### Creating behaviors and streams
Expand Down Expand Up @@ -450,7 +450,7 @@ occurred the consumer is immediately pushed to.

#### `empty: Stream<any>`

Empty stream.
Empty stream.

#### ~`Stream.of<A>(a: A): Stream<A>`~

Expand Down Expand Up @@ -502,7 +502,7 @@ considered. If it is `true` then the returned stream also has the
occurrence—otherwise it doesn't. The behavior works as a filter that
decides whether or not values are let through.

#### `scanS<A, B>(fn: (a: A, b: B) => B, startingValue: B, stream: Stream<A>): Behavior<Stream<B>>`
#### `scanFrom<A, B>(fn: (a: A, b: B) => B, startingValue: B, stream: Stream<A>): Behavior<Stream<B>>`

A stateful scan.

Expand Down Expand Up @@ -546,6 +546,7 @@ always "switches" to the current stream at the behavior.
```ts
changes<A>(b: Behavior<A>, comparator: (v: A, u: A) => boolean = (v, u) => v === u): Stream<A>;
```

Takes a behavior and returns a stream that has an occurrence whenever
the behaviors value changes.

Expand Down Expand Up @@ -608,7 +609,7 @@ contionusly changing, like `Date.now`.

Returns `true` if `b` is a behavior and `false` otherwise.

#### `when(b: Behavior<boolean>): Behavior<Future<{}>>`
#### `whenFrom(b: Behavior<boolean>): Behavior<Future<{}>>`

Takes a boolean valued behavior an returns a behavior that at any
point in time contains a future that occurs in the next moment where
Expand All @@ -626,15 +627,15 @@ future occurs.
Creates a new behavior that acts exactly like `initial` until `next`
occurs after which it acts like the behavior it contains.

#### `switcher<A>(init: Behavior<A>, s: Stream<Behavior<A>>): Behavior<Behavior<A>>`
#### `switcherFrom<A>(init: Behavior<A>, s: Stream<Behavior<A>>): Behavior<Behavior<A>>`

A behavior of a behavior that switches to the latest behavior from `s`.

#### `stepper<B>(initial: B, steps: Stream<B>): Behavior<Behavior<B>>`
#### `stepperFrom<B>(initial: B, steps: Stream<B>): Behavior<Behavior<B>>`

Creates a behavior whose value is the last occurrence in the stream.

#### `scan<A, B>(fn: (a: A, b: B) => B, init: B, source: Stream<A>): Behavior<Behavior<B>>`
#### `scanFrom<A, B>(fn: (a: A, b: B) => B, init: B, source: Stream<A>): Behavior<Behavior<B>>`

The returned behavior initially has the initial value, on each
occurrence in `source` the function is applied to the current value of
Expand All @@ -653,7 +654,7 @@ behavior gives a behavior with values that contain the difference
between the current sample time and the time at which the outer
behavior was sampled.

#### `integrate(behavior: Behavior<number>): Behavior<Behavior<number>>`
#### `integrateFrom(behavior: Behavior<number>): Behavior<Behavior<number>>`

Integrate behavior with respect to time.

Expand Down
14 changes: 11 additions & 3 deletions src/animation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { go } from "@funkia/jabz";
import { Behavior, stepper, time, scan, Stream, snapshot, lift } from ".";
import {
Behavior,
stepperFrom,
time,
accumFrom,
Stream,
snapshot,
lift
} from ".";

export type TimingFunction = (t: number) => number;

Expand All @@ -21,13 +29,13 @@ export function transitionBehavior(
timeB: Behavior<number> = time
): Behavior<Behavior<number>> {
return go(function*(): any {
const rangeValueB: Behavior<Range> = yield scan(
const rangeValueB: Behavior<Range> = yield accumFrom(
(newV, prev) => ({ from: prev.to, to: newV }),
{ from: initial, to: initial },
triggerStream
);
const initialStartTime: number = yield timeB;
const startTimeB: Behavior<number> = yield stepper(
const startTimeB: Behavior<number> = yield stepperFrom(
initialStartTime,
snapshot(timeB, triggerStream)
);
Expand Down
Loading

0 comments on commit f5ac041

Please sign in to comment.