behavior-graph
Advanced tools
Comparing version
@@ -23,2 +23,11 @@ var bg = (function (exports) { | ||
} | ||
peek() { | ||
if (this.length == 0) { | ||
return undefined; | ||
} | ||
else { | ||
this.heapifyBuffer(); | ||
return this.queue[0]; | ||
} | ||
} | ||
pop() { | ||
@@ -29,11 +38,3 @@ if (this.length == 0) { | ||
else { | ||
// heapify elements in the buffer | ||
for (let i = 0; i < this.buffer.length; i++) { | ||
let b = this.buffer[i]; | ||
this.queue.push(b); | ||
if (this.queue.length > 1) { | ||
this.up(this.queue.length - 1); | ||
} | ||
} | ||
this.buffer.length = 0; | ||
this.heapifyBuffer(); | ||
// The min is the first one | ||
@@ -52,2 +53,13 @@ // We want to pop from the front (shift) but that is | ||
} | ||
heapifyBuffer() { | ||
// heapify elements in the buffer | ||
for (let i = 0; i < this.buffer.length; i++) { | ||
let b = this.buffer[i]; | ||
this.queue.push(b); | ||
if (this.queue.length > 1) { | ||
this.up(this.queue.length - 1); | ||
} | ||
} | ||
this.buffer.length = 0; | ||
} | ||
get length() { | ||
@@ -112,2 +124,3 @@ return this.queue.length + this.buffer.length; | ||
skipChecks = false; | ||
didUpdateSubscribers; | ||
constructor(extent, name) { | ||
@@ -172,2 +185,21 @@ this.extent = extent; | ||
} | ||
subscribeToJustUpdated(callback) { | ||
// returns an unsubscribe callback that caller can call when no longer needed | ||
if (this.didUpdateSubscribers === undefined) { | ||
this.didUpdateSubscribers = new Set(); | ||
} | ||
this.didUpdateSubscribers.add(callback); | ||
return (() => { | ||
this.didUpdateSubscribers.delete(callback); | ||
}); | ||
} | ||
notifyJustUpdatedSubscribers() { | ||
if (this.didUpdateSubscribers !== undefined && this.didUpdateSubscribers.size > 0) { | ||
this.extent.sideEffect(ext => { | ||
this.didUpdateSubscribers?.forEach(function (callback) { | ||
callback(); | ||
}); | ||
}); | ||
} | ||
} | ||
} | ||
@@ -204,2 +236,3 @@ class Moment extends Resource { | ||
this._happenedWhen = this.graph.currentEvent; | ||
this.notifyJustUpdatedSubscribers(); | ||
this.graph.resourceTouched(this); | ||
@@ -234,2 +267,5 @@ this.graph.trackTransient(this); | ||
this.assertValidUpdater(); | ||
this._updateForce(newValue); | ||
} | ||
_updateForce(newValue) { | ||
if (this.graph.currentEvent != null && this.currentState.event.sequence < this.graph.currentEvent?.sequence) { | ||
@@ -240,2 +276,3 @@ // captures trace as the value before any updates | ||
this.currentState = { value: newValue, event: this.graph.currentEvent }; | ||
this.notifyJustUpdatedSubscribers(); | ||
this.graph.resourceTouched(this); | ||
@@ -514,7 +551,25 @@ this.graph.trackTransient(this); | ||
runNextBehavior(sequence) { | ||
let behavior = this.activatedBehaviors.pop(); | ||
if (behavior != undefined && behavior.removedWhen != sequence) { | ||
this.currentBehavior = behavior; | ||
behavior.block(behavior.extent); | ||
this.currentBehavior = null; | ||
// take top behavior off queue | ||
let topBehavior = this.activatedBehaviors.pop(); | ||
// if no behavior left we quit | ||
while (topBehavior !== undefined) { | ||
if (topBehavior.removedWhen == sequence) { | ||
// if this behavior has been removed already then try next one | ||
topBehavior = this.activatedBehaviors.pop(); | ||
} | ||
else { | ||
// valid behavior, run it | ||
this.currentBehavior = topBehavior; | ||
topBehavior.block(topBehavior.extent); | ||
this.currentBehavior = null; | ||
// check if there is a next behavior and it's the same ordering as the first behavior | ||
let nextBehavior = this.activatedBehaviors.peek(); | ||
if (nextBehavior !== undefined && nextBehavior.order == topBehavior.order) { | ||
// if so we will try running it also | ||
topBehavior = this.activatedBehaviors.pop(); | ||
} | ||
else { | ||
break; | ||
} | ||
} | ||
} | ||
@@ -866,3 +921,5 @@ } | ||
this.extentsAdded.push(extent); | ||
this.activateBehavior(extent.addedToGraphBehavior, this.currentEvent.sequence); | ||
// this casting below is a hack to get at the private method so we can skip integrity checks which | ||
// allow us to update addedToGraph from inside whatever behavior or action it is added | ||
extent.addedToGraph._updateForce(true); | ||
for (let behavior of extent.behaviors) { | ||
@@ -1167,3 +1224,2 @@ this.addBehavior(behavior); | ||
addedToGraph; | ||
addedToGraphBehavior; | ||
lifetime = null; | ||
@@ -1180,8 +1236,3 @@ static removeContainedLifetimes = exports.ExtentRemoveStrategy.containedLifetimes; | ||
this.graph = graph; | ||
// this hidden behavior supplies addedToGraph and gets activated independently when an | ||
// extent is added to the graph | ||
this.addedToGraph = new State(this, false); | ||
this.addedToGraphBehavior = this.behavior().supplies(this.addedToGraph).runs((extent) => { | ||
extent.addedToGraph.update(true); | ||
}); | ||
} | ||
@@ -1188,0 +1239,0 @@ unifyLifetime(extent) { |
@@ -9,3 +9,5 @@ export interface Orderable { | ||
push(behavior: T): void; | ||
peek(): T | undefined; | ||
pop(): T | undefined; | ||
private heapifyBuffer; | ||
get length(): number; | ||
@@ -12,0 +14,0 @@ unsort(): void; |
@@ -23,2 +23,11 @@ "use strict"; | ||
} | ||
peek() { | ||
if (this.length == 0) { | ||
return undefined; | ||
} | ||
else { | ||
this.heapifyBuffer(); | ||
return this.queue[0]; | ||
} | ||
} | ||
pop() { | ||
@@ -29,11 +38,3 @@ if (this.length == 0) { | ||
else { | ||
// heapify elements in the buffer | ||
for (let i = 0; i < this.buffer.length; i++) { | ||
let b = this.buffer[i]; | ||
this.queue.push(b); | ||
if (this.queue.length > 1) { | ||
this.up(this.queue.length - 1); | ||
} | ||
} | ||
this.buffer.length = 0; | ||
this.heapifyBuffer(); | ||
// The min is the first one | ||
@@ -52,2 +53,13 @@ // We want to pop from the front (shift) but that is | ||
} | ||
heapifyBuffer() { | ||
// heapify elements in the buffer | ||
for (let i = 0; i < this.buffer.length; i++) { | ||
let b = this.buffer[i]; | ||
this.queue.push(b); | ||
if (this.queue.length > 1) { | ||
this.up(this.queue.length - 1); | ||
} | ||
} | ||
this.buffer.length = 0; | ||
} | ||
get length() { | ||
@@ -54,0 +66,0 @@ return this.queue.length + this.buffer.length; |
@@ -29,3 +29,2 @@ import { Graph } from "./graph.js"; | ||
addedToGraph: State<boolean>; | ||
addedToGraphBehavior: Behavior; | ||
lifetime: ExtentLifetime | null; | ||
@@ -35,5 +34,5 @@ static readonly removeContainedLifetimes = ExtentRemoveStrategy.containedLifetimes; | ||
constructor(graph: Graph); | ||
unifyLifetime(extent: Extent): void; | ||
addChildLifetime(extent: Extent): void; | ||
hasCompatibleLifetime(extent: Extent): boolean; | ||
unifyLifetime<T extends Extent>(extent: T): void; | ||
addChildLifetime<T extends Extent>(extent: T): void; | ||
hasCompatibleLifetime<T extends Extent>(extent: T): boolean; | ||
addBehavior(behavior: Behavior): void; | ||
@@ -40,0 +39,0 @@ addResource(resource: Resource): void; |
@@ -130,8 +130,3 @@ "use strict"; | ||
this.graph = graph; | ||
// this hidden behavior supplies addedToGraph and gets activated independently when an | ||
// extent is added to the graph | ||
this.addedToGraph = new resource_js_1.State(this, false); | ||
this.addedToGraphBehavior = this.behavior().supplies(this.addedToGraph).runs((extent) => { | ||
extent.addedToGraph.update(true); | ||
}); | ||
} | ||
@@ -138,0 +133,0 @@ unifyLifetime(extent) { |
@@ -250,7 +250,25 @@ "use strict"; | ||
runNextBehavior(sequence) { | ||
let behavior = this.activatedBehaviors.pop(); | ||
if (behavior != undefined && behavior.removedWhen != sequence) { | ||
this.currentBehavior = behavior; | ||
behavior.block(behavior.extent); | ||
this.currentBehavior = null; | ||
// take top behavior off queue | ||
let topBehavior = this.activatedBehaviors.pop(); | ||
// if no behavior left we quit | ||
while (topBehavior !== undefined) { | ||
if (topBehavior.removedWhen == sequence) { | ||
// if this behavior has been removed already then try next one | ||
topBehavior = this.activatedBehaviors.pop(); | ||
} | ||
else { | ||
// valid behavior, run it | ||
this.currentBehavior = topBehavior; | ||
topBehavior.block(topBehavior.extent); | ||
this.currentBehavior = null; | ||
// check if there is a next behavior and it's the same ordering as the first behavior | ||
let nextBehavior = this.activatedBehaviors.peek(); | ||
if (nextBehavior !== undefined && nextBehavior.order == topBehavior.order) { | ||
// if so we will try running it also | ||
topBehavior = this.activatedBehaviors.pop(); | ||
} | ||
else { | ||
break; | ||
} | ||
} | ||
} | ||
@@ -604,3 +622,5 @@ } | ||
this.extentsAdded.push(extent); | ||
this.activateBehavior(extent.addedToGraphBehavior, this.currentEvent.sequence); | ||
// this casting below is a hack to get at the private method so we can skip integrity checks which | ||
// allow us to update addedToGraph from inside whatever behavior or action it is added | ||
extent.addedToGraph._updateForce(true); | ||
for (let behavior of extent.behaviors) { | ||
@@ -607,0 +627,0 @@ this.addBehavior(behavior); |
@@ -20,2 +20,3 @@ import { Behavior } from "./behavior.js"; | ||
private skipChecks; | ||
private didUpdateSubscribers?; | ||
constructor(extent: Extent, name?: string); | ||
@@ -28,2 +29,4 @@ get order(): Demandable; | ||
get justUpdated(): boolean; | ||
subscribeToJustUpdated(callback: () => void): (() => void); | ||
protected notifyJustUpdatedSubscribers(): void; | ||
} | ||
@@ -53,2 +56,3 @@ export declare class Moment<T = undefined> extends Resource implements Transient { | ||
updateForce(newValue: T): void; | ||
private _updateForce; | ||
clear(): void; | ||
@@ -55,0 +59,0 @@ get value(): T; |
@@ -78,2 +78,22 @@ "use strict"; | ||
} | ||
subscribeToJustUpdated(callback) { | ||
// returns an unsubscribe callback that caller can call when no longer needed | ||
if (this.didUpdateSubscribers === undefined) { | ||
this.didUpdateSubscribers = new Set(); | ||
} | ||
this.didUpdateSubscribers.add(callback); | ||
return (() => { | ||
this.didUpdateSubscribers.delete(callback); | ||
}); | ||
} | ||
notifyJustUpdatedSubscribers() { | ||
if (this.didUpdateSubscribers !== undefined && this.didUpdateSubscribers.size > 0) { | ||
this.extent.sideEffect(ext => { | ||
var _a; | ||
(_a = this.didUpdateSubscribers) === null || _a === void 0 ? void 0 : _a.forEach(function (callback) { | ||
callback(); | ||
}); | ||
}); | ||
} | ||
} | ||
} | ||
@@ -114,2 +134,3 @@ exports.Resource = Resource; | ||
this._happenedWhen = this.graph.currentEvent; | ||
this.notifyJustUpdatedSubscribers(); | ||
this.graph.resourceTouched(this); | ||
@@ -143,4 +164,7 @@ this.graph.trackTransient(this); | ||
updateForce(newValue) { | ||
this.assertValidUpdater(); | ||
this._updateForce(newValue); | ||
} | ||
_updateForce(newValue) { | ||
var _a; | ||
this.assertValidUpdater(); | ||
if (this.graph.currentEvent != null && this.currentState.event.sequence < ((_a = this.graph.currentEvent) === null || _a === void 0 ? void 0 : _a.sequence)) { | ||
@@ -151,2 +175,3 @@ // captures trace as the value before any updates | ||
this.currentState = { value: newValue, event: this.graph.currentEvent }; | ||
this.notifyJustUpdatedSubscribers(); | ||
this.graph.resourceTouched(this); | ||
@@ -153,0 +178,0 @@ this.graph.trackTransient(this); |
@@ -9,3 +9,5 @@ export interface Orderable { | ||
push(behavior: T): void; | ||
peek(): T | undefined; | ||
pop(): T | undefined; | ||
private heapifyBuffer; | ||
get length(): number; | ||
@@ -12,0 +14,0 @@ unsort(): void; |
@@ -20,2 +20,11 @@ // | ||
} | ||
peek() { | ||
if (this.length == 0) { | ||
return undefined; | ||
} | ||
else { | ||
this.heapifyBuffer(); | ||
return this.queue[0]; | ||
} | ||
} | ||
pop() { | ||
@@ -26,11 +35,3 @@ if (this.length == 0) { | ||
else { | ||
// heapify elements in the buffer | ||
for (let i = 0; i < this.buffer.length; i++) { | ||
let b = this.buffer[i]; | ||
this.queue.push(b); | ||
if (this.queue.length > 1) { | ||
this.up(this.queue.length - 1); | ||
} | ||
} | ||
this.buffer.length = 0; | ||
this.heapifyBuffer(); | ||
// The min is the first one | ||
@@ -49,2 +50,13 @@ // We want to pop from the front (shift) but that is | ||
} | ||
heapifyBuffer() { | ||
// heapify elements in the buffer | ||
for (let i = 0; i < this.buffer.length; i++) { | ||
let b = this.buffer[i]; | ||
this.queue.push(b); | ||
if (this.queue.length > 1) { | ||
this.up(this.queue.length - 1); | ||
} | ||
} | ||
this.buffer.length = 0; | ||
} | ||
get length() { | ||
@@ -51,0 +63,0 @@ return this.queue.length + this.buffer.length; |
@@ -29,3 +29,2 @@ import { Graph } from "./graph.js"; | ||
addedToGraph: State<boolean>; | ||
addedToGraphBehavior: Behavior; | ||
lifetime: ExtentLifetime | null; | ||
@@ -35,5 +34,5 @@ static readonly removeContainedLifetimes = ExtentRemoveStrategy.containedLifetimes; | ||
constructor(graph: Graph); | ||
unifyLifetime(extent: Extent): void; | ||
addChildLifetime(extent: Extent): void; | ||
hasCompatibleLifetime(extent: Extent): boolean; | ||
unifyLifetime<T extends Extent>(extent: T): void; | ||
addChildLifetime<T extends Extent>(extent: T): void; | ||
hasCompatibleLifetime<T extends Extent>(extent: T): boolean; | ||
addBehavior(behavior: Behavior): void; | ||
@@ -40,0 +39,0 @@ addResource(resource: Resource): void; |
@@ -113,3 +113,2 @@ // | ||
addedToGraph; | ||
addedToGraphBehavior; | ||
lifetime = null; | ||
@@ -126,8 +125,3 @@ static removeContainedLifetimes = ExtentRemoveStrategy.containedLifetimes; | ||
this.graph = graph; | ||
// this hidden behavior supplies addedToGraph and gets activated independently when an | ||
// extent is added to the graph | ||
this.addedToGraph = new State(this, false); | ||
this.addedToGraphBehavior = this.behavior().supplies(this.addedToGraph).runs((extent) => { | ||
extent.addedToGraph.update(true); | ||
}); | ||
} | ||
@@ -134,0 +128,0 @@ unifyLifetime(extent) { |
@@ -235,7 +235,25 @@ // | ||
runNextBehavior(sequence) { | ||
let behavior = this.activatedBehaviors.pop(); | ||
if (behavior != undefined && behavior.removedWhen != sequence) { | ||
this.currentBehavior = behavior; | ||
behavior.block(behavior.extent); | ||
this.currentBehavior = null; | ||
// take top behavior off queue | ||
let topBehavior = this.activatedBehaviors.pop(); | ||
// if no behavior left we quit | ||
while (topBehavior !== undefined) { | ||
if (topBehavior.removedWhen == sequence) { | ||
// if this behavior has been removed already then try next one | ||
topBehavior = this.activatedBehaviors.pop(); | ||
} | ||
else { | ||
// valid behavior, run it | ||
this.currentBehavior = topBehavior; | ||
topBehavior.block(topBehavior.extent); | ||
this.currentBehavior = null; | ||
// check if there is a next behavior and it's the same ordering as the first behavior | ||
let nextBehavior = this.activatedBehaviors.peek(); | ||
if (nextBehavior !== undefined && nextBehavior.order == topBehavior.order) { | ||
// if so we will try running it also | ||
topBehavior = this.activatedBehaviors.pop(); | ||
} | ||
else { | ||
break; | ||
} | ||
} | ||
} | ||
@@ -587,3 +605,5 @@ } | ||
this.extentsAdded.push(extent); | ||
this.activateBehavior(extent.addedToGraphBehavior, this.currentEvent.sequence); | ||
// this casting below is a hack to get at the private method so we can skip integrity checks which | ||
// allow us to update addedToGraph from inside whatever behavior or action it is added | ||
extent.addedToGraph._updateForce(true); | ||
for (let behavior of extent.behaviors) { | ||
@@ -590,0 +610,0 @@ this.addBehavior(behavior); |
@@ -20,2 +20,3 @@ import { Behavior } from "./behavior.js"; | ||
private skipChecks; | ||
private didUpdateSubscribers?; | ||
constructor(extent: Extent, name?: string); | ||
@@ -28,2 +29,4 @@ get order(): Demandable; | ||
get justUpdated(): boolean; | ||
subscribeToJustUpdated(callback: () => void): (() => void); | ||
protected notifyJustUpdatedSubscribers(): void; | ||
} | ||
@@ -53,2 +56,3 @@ export declare class Moment<T = undefined> extends Resource implements Transient { | ||
updateForce(newValue: T): void; | ||
private _updateForce; | ||
clear(): void; | ||
@@ -55,0 +59,0 @@ get value(): T; |
@@ -18,2 +18,3 @@ // | ||
skipChecks = false; | ||
didUpdateSubscribers; | ||
constructor(extent, name) { | ||
@@ -78,2 +79,21 @@ this.extent = extent; | ||
} | ||
subscribeToJustUpdated(callback) { | ||
// returns an unsubscribe callback that caller can call when no longer needed | ||
if (this.didUpdateSubscribers === undefined) { | ||
this.didUpdateSubscribers = new Set(); | ||
} | ||
this.didUpdateSubscribers.add(callback); | ||
return (() => { | ||
this.didUpdateSubscribers.delete(callback); | ||
}); | ||
} | ||
notifyJustUpdatedSubscribers() { | ||
if (this.didUpdateSubscribers !== undefined && this.didUpdateSubscribers.size > 0) { | ||
this.extent.sideEffect(ext => { | ||
this.didUpdateSubscribers?.forEach(function (callback) { | ||
callback(); | ||
}); | ||
}); | ||
} | ||
} | ||
} | ||
@@ -110,2 +130,3 @@ export class Moment extends Resource { | ||
this._happenedWhen = this.graph.currentEvent; | ||
this.notifyJustUpdatedSubscribers(); | ||
this.graph.resourceTouched(this); | ||
@@ -140,2 +161,5 @@ this.graph.trackTransient(this); | ||
this.assertValidUpdater(); | ||
this._updateForce(newValue); | ||
} | ||
_updateForce(newValue) { | ||
if (this.graph.currentEvent != null && this.currentState.event.sequence < this.graph.currentEvent?.sequence) { | ||
@@ -146,2 +170,3 @@ // captures trace as the value before any updates | ||
this.currentState = { value: newValue, event: this.graph.currentEvent }; | ||
this.notifyJustUpdatedSubscribers(); | ||
this.graph.resourceTouched(this); | ||
@@ -148,0 +173,0 @@ this.graph.trackTransient(this); |
{ | ||
"name": "behavior-graph", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"main": "lib/cjs/index.js", | ||
@@ -19,4 +19,7 @@ "module": "lib/mjs/index.js", | ||
"redux", | ||
"recoil", | ||
"mobx", | ||
"state machine", | ||
"store", | ||
"rxjs", | ||
"effects", | ||
@@ -29,3 +32,3 @@ "reactive", | ||
"@types/jest": "^27.4.0", | ||
"@types/node": "^16.4.2", | ||
"@types/node": "^16.18.11", | ||
"jest": "^27.4.5", | ||
@@ -32,0 +35,0 @@ "rollup": "^2.58.3", |
@@ -1,5 +0,6 @@ | ||
Behavior Graph lets you build your program out of small, easily understood pieces in a way that lets the computer do more of the work for you. | ||
It is an architecture and supporting library that simplifies the type of complexity that comes with event-driven software such as user facing applications and control systems. | ||
Behavior Graph lets you build your programs out of small, easily understood pieces in a way that lets the computer do more of the work for you. | ||
It is an architecture and supporting library that simplifies the type of complexity that comes with event-driven software, such as user facing applications and control systems. | ||
It's also a fun way to program. | ||
@@ -18,12 +19,21 @@ | ||
Or maybe you're the type of person who likes nerdy new software ideas. (Seriously though, who doesn't, amirite?) We guarantee you will find Behavior Graph interesting. | ||
It's also possible you're the type of person who likes nerdy new software ideas. (Seriously though, who doesn't, amirite?) If that's the case, we guarantee you will find Behavior Graph interesting. | ||
## Can I see an example? | ||
Behavior Graph introduces a handful of new concepts. | ||
These concepts aren't difficult, but you will require some orientation. | ||
* We've created a [short walk-through of a Login form](https://yahoo.github.io/bgdocs/docs/js-and-typescript/code-example/) using Behavior Graph. | ||
* You can also take a look at [one of our tutorials](https://yahoo.github.io/bgdocs/docs/js-and-typescript/tutorial-1/). | ||
## How does it Work? | ||
As programmers it is natural to partition our software into smaller tasks. Consider a typical Login form. | ||
As programmers it's natural to partition our software into subtasks. For example, let's consider what happens on a typical login form. | ||
1. When a user clicks on the Login button, we want to validate the Email and Password fields. | ||
2. If validation passes we want to make a network call to log the user in. | ||
3. And we want to update the UI to provide feedback in case the validation fails, or disable the login button while we are actively logging in. | ||
2. If validation passes, then we want to make a network call to log the user in. | ||
3. Additionally we want to update the UI to provide feedback in case the validation fails, or disable the login button while we are actively logging in. | ||
Most programming languages offer __functions__ as the primary tool for creating these subtasks. The code for our Login form will have `validateFields`, `networkLogin`, and `updateUI` functions. There will also be an `onLoginClick` function that looks like this: | ||
Most programming languages offer __functions__ as the primary tool for creating these subtasks. Conceptually we have three subtasks. So we will create three corresponding functions: `validateFields`, `networkLogin`, and `updateUI`. We will also need an additional `onLoginClick` function to run these tasks. It will look like this: | ||
@@ -38,3 +48,3 @@ ```javascript | ||
These functions are not independent, however. There are __dependency relationships__ between them that need to be respected. | ||
Our four functions are not independent, however. There are __dependency relationships__ between them that need to be respected. | ||
* `validateFields` depends on `onLoginClick` being run. | ||
@@ -44,15 +54,16 @@ * `networkLogin` depends on `onLoginClick` being run, and it depends on the results of `validateFields`. | ||
__Functions__ cannot express dependency relationships directly. Instead we must call functions in a particular order to uphold these relationships. | ||
If we call `networkLogin()` before `validateFields()` the feature won't work. It has to be after. `networkLogin` depends on the validation in `validateFields` succeeding. | ||
We could partially encode the dependency relationships by using parameters and return values (aka functional programming). This gives us additional structure, but it doesn't remove the need to call these functions in a correct order. Calling something like `networkLogin(validateFields())` is still a sequence of function calls. It's just sideways. | ||
But looking at the code, there is nothing that says, `networkLogin` depends on `validateFields`. Instead, we implement this relationship by calling our functions in a specific order. If we were to call `networkLogin()` before `validateFields()`, the feature wouldn't work. | ||
The problem is that expressing dependency relationships in terms of sequenced function calls means work for the developer: | ||
* There's work to get it correct. | ||
* There's work to rearrange calls as dependencies (inevitably) change. | ||
* There's work mentally translating back from sequenced function calls to the original dependency relationships in order to understand the intent of the code. | ||
* And there's work fixing errors whenever any of these efforts go wrong. | ||
So as long as we are organizing our code using only functions, we will need to implement part of our logic in terms of ordered function calls. This is because function definitions cannot express dependency relationships directly. There is no other way. | ||
But maybe all this work isn't necessary. What if functions _could_ express dependency relationships? | ||
We could do more by using parameters and return values (aka functional programming), but that still wouldn't remove the need to call these functions in a valid order. Calling something like `networkLogin(validateFields())` is still a sequence of function calls. | ||
It is the job of the developer to translate these dependency relationships into sequenced function calls. This requires time and effort: | ||
* There's work to get it correct, which is often much more difficult than in our example here. | ||
* Features inevitably change, which means dependencies inevitably change. So there's work in updating our existing sequences. | ||
* When reading code, we must mentally translate back from function calls to the original dependency relationships. This is so we can understand the intent of the code. This is also work. | ||
* And finally there's work fixing errors whenever any of these efforts go wrong. | ||
We're proposing that maybe all this work isn't necessary. What if function definitions _could_ express dependency relationships? | ||
__Behavior Graph__ is a library that provides this alternative. It introduces a new unit of code organization called the __behavior__. It is a block of code together with its dependency relationships. | ||
@@ -64,22 +75,14 @@ | ||
1. _Control flow for free_: The computer uses the dependency relationships to run our behaviors in the correct sequence. This works just like spreadsheet formulas. | ||
2. _Ease of maintenance_: Requirements inevitably change, and do dependencies. Control flow automatically adapts. | ||
3. _Legibility_: Dependency relationships are explicit. We can look at a behavior and immediately see how it interfaces with other behaviors. This is unlike functions which are often linked via calls made in some other part(s) of the code. | ||
2. _Ease of maintenance_: Requirements inevitably change. This means dependencies change. Control flow automatically adapts. | ||
3. _Legibility_: Dependency relationships are explicit. We can look at a behavior and immediately see how it interfaces with other behaviors. This is unlike function definitions which are linked via calls in some other part of the program. | ||
Behavior Graph isn't a replacement for functions. (We wrote it with functions, hello!) Instead it gives us a higher level of abstraction for partitioning our code into subtasks. It lets us say "these two blocks of code are related and here's how". And with that information both humans and the computer are better able to infer the intent of the code. | ||
Behavior Graph isn't a replacement for functions. (We wrote it with functions, hello!) Instead it gives us higher level abstractions for partitioning our code into subtasks. It lets us say "these two blocks of code are related and here's how". And with that information the computer is able to run things for us. And humans are better able to infer the intent of the code. | ||
## Can I see an example? | ||
Behavior Graph introduces a handful of new concepts. | ||
These concepts aren't difficult, but you will require some orientation. | ||
* We've created a [short walk-through of a Login form](https://yahoo.github.io/bgdocs/docs/typescript/code-example/) using Behavior Graph. | ||
* You can also take a look at [one of our tutorials](https://yahoo.github.io/bgdocs/docs/typescript/tutorials/tutorial-1/). | ||
## Small | ||
Behavior Graph is a small library. It's around 1500 lines of formatted code. It has no dependencies. The Javascript is less than 6KB minified + gzip'd. | ||
Behavior Graph is a small library. It's around 1500 lines of formatted code. It has no dependencies. | ||
## Incremental | ||
It is easy to introduce into a codebase incrementally. It is designed to work side by side with existing code. We gradually migrated Yahoo's video playing library, while in production, as we became confident in Behavior Graph's feature set. | ||
It is easy to introduce into a codebase incrementally. It is designed to work side by side with existing code. We went through this incremental migration process ourselves. | ||
@@ -99,2 +102,12 @@ ## Scale | ||
## Should I Use it in my Project? | ||
This Javascript/Typescript version is not used in production at Yahoo currently. It is a direct port from the original Objective-C. It has excellent test coverage. We are confident it works as intended. | ||
But it is also newly open sourced. You won't find blog posts and Stack Overflow answers to your questions. If you are on a team that expects that type of support you should proceed with caution. | ||
If you are building a browser based app using imperative UI libraries such as JQuery or direct DOM manipulation we think you should be fine. But if you are using any of the popular reactive UI frameworks such as React, Angular, or Ember you will need to figure out how to make that work. We do not have existing adapters. | ||
Would like to help us with any of these adapters? We would certainly love to have your help. Please reach out to us on [discord](https://discord.gg/5mvat8tc7d). | ||
## Obtaining Behavior Graph | ||
@@ -111,9 +124,9 @@ | ||
[Go here for the full documentation site](https://yahoo.github.io/bgdocs/docs/typescript/tutorials/). | ||
[Go here for the full documentation site](https://yahoo.github.io/bgdocs/docs/). | ||
While there are only a handful of basic concepts in Behavior Graph, it does require a shift in thinking. We recommend you start with the [Getting Started guide](https://yahoo.github.io/bgdocs/docs/typescript/quickstart/) then work through the [Tutorials](https://yahoo.github.io/bgdocs/docs/typescript/tutorials/tutorial-1/). | ||
While there are only a handful of basic concepts in Behavior Graph, it does require a shift in thinking. We recommend you start with the [Getting Started guide](https://yahoo.github.io/bgdocs/docs/js-and-typescript/quickstart/) then work through the [Tutorials](https://yahoo.github.io/bgdocs/docs/js-and-typescript/tutorial-1/). | ||
## Contact Us | ||
We really do like talking about Behavior Graph. | ||
We really do need help and we really do like talking about Behavior Graph. | ||
Discord is a good place for that. | ||
@@ -125,4 +138,4 @@ | ||
* Yes there are many interesting areas for contribution. Please ask us. | ||
* Don't just make a pull request. We would prefer to let you know upfront if your idea is unlikely to be accepted. | ||
* Yes, there are many interesting areas for contribution. Please ask us. | ||
* Don't just make a pull request. Talk to us first. We don't want to see you put in effort on something that won't get accepted. | ||
@@ -183,2 +196,2 @@ ## Comparisons | ||
But this is exactly what we do as programmers on a daily basis. | ||
But this is exactly what we do as programmers on a daily basis. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
289916
3.45%4671
3.64%189
6.78%