Skip to content

Commit

Permalink
Merge pull request #5 from yahoo/sal/v070branch
Browse files Browse the repository at this point in the history
0.7.0 Improvements
  • Loading branch information
slevin authored Oct 5, 2022
2 parents d5a3f13 + 89a4714 commit b1fe6df
Show file tree
Hide file tree
Showing 73 changed files with 1,536 additions and 601 deletions.
35 changes: 17 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,22 @@ We originally developed it for ourselves to use in a video playing library at Ya

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/jvm/code-example/) using Behavior Graph.
* You can also take a look at [one of our tutorials](https://yahoo.github.io/bgdocs/docs/jvm/tutorial-1/).


## How does it Work?

As programmers it's natural to partition our software into subtasks. For example, let's consider what happens on 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, 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.
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. 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:

Expand Down Expand Up @@ -65,16 +74,6 @@ This gives us:

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?

__We are updating our Kotlin documentation. Below are links to our Javascript/Typescript port which has a very similar API__

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.
Expand All @@ -98,21 +97,21 @@ Behavior Graph has been ported to multiple platforms.

## Should I Use it in my Project?

This Kotlin 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.
This Kotlin is minimally used 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.

## Obtaining Behavior Graph

Currently available only in source form on Github.
Behavior Graph is available in source form on [Github](https://github.com/yahoo/bgkotlin).

## Documentation
It is also available on Maven Central @ [com.yahoo.behaviorgraph/bgjvm](https://search.maven.org/artifact/com.yahoo.behaviorgraph/bgjvm).

__We are updating our Kotlin documentation. Below are links to our Javascript/Typescript port which has a very similar API__
## Documentation

[Go here for the full documentation site](https://yahoo.github.io/bgdocs/docs/typescript/).
[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/jvm/quickstart/) then work through the [Tutorials](https://yahoo.github.io/bgdocs/docs/jvm/tutorial-1/).

## Contact Us

Expand Down
36 changes: 30 additions & 6 deletions behavior-graph/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ java {
withSourcesJar()
}

tasks.named('jar') {
manifest {
attributes('Automatic-Module-Name': 'behaviorgraph')
}
}

def dokkaJavadocJar = tasks.register("dokkaJavadocJar", org.gradle.jvm.tasks.Jar) {
it.dependsOn(dokkaJavadoc)
it.from(dokkaJavadoc.outputDirectory)
Expand All @@ -23,13 +29,29 @@ def dokkaJavadocJar = tasks.register("dokkaJavadocJar", org.gradle.jvm.tasks.Jar
}

publishing {
repositories {
maven {
def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
url = releasesRepoUrl //version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials {
if (project.hasProperty("ossrhUsername")) {
username = ossrhUsername
password = ossrhPassword
}
}
}
}
publications {
release(MavenPublication) {
from components.java
groupId = 'com.yahoo.behavior-graph'
artifactId = 'bgkotlin'
version = '0.5.0-RC1'
groupId = 'com.yahoo.behaviorgraph'
artifactId = 'bgjvm'
version = '0.7.0'
artifact dokkaJavadocJar
repositories {

}
pom {
name = 'Behavior Graph'
description = '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.'
Expand Down Expand Up @@ -57,12 +79,14 @@ publishing {
}
}


signing {
sign configurations.archives
sign publishing.publications.release
if (project.hasProperty("signing.keyId")) {
sign configurations.archives
sign publishing.publications.release
}
}


dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// Copyright Yahoo 2021
//
package com.yahoo.behaviorgraph
package behaviorgraph

/**
* An __Action__ is a block of code which initiates a Behavior Graph [Event].
Expand All @@ -18,14 +18,15 @@ internal interface RunnableAction: Action {
fun runAction()
}

internal class GraphAction(val block: () -> Unit, override val debugName: String? = null): RunnableAction {
internal class GraphAction(val thunk: Thunk, override val debugName: String? = null): RunnableAction {
override fun runAction() {
block()
thunk.invoke()
}
}

internal class ExtentAction(val block: (extent: Extent) -> Unit, val extent: Extent, override val debugName: String? = null): RunnableAction {
internal class ExtentAction<T>(val thunk: ExtentThunk<T>, val context: T, override val debugName: String? = null):
RunnableAction {
override fun runAction() {
block(extent)
thunk.invoke(context)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//
// Copyright Yahoo 2021
//
package behaviorgraph

class AllDemandsMustBeAddedToTheGraphExceptions(s: String, val currentBehavior: Behavior<*>, val untrackedDemand: Resource) : BehaviorGraphException("$s Behavior=$currentBehavior untrackedDemand=$untrackedDemand")
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
//
// Copyright Yahoo 2021
//
package com.yahoo.behaviorgraph
package behaviorgraph

/**
* A behavior is a block of code together with its dependency relationships (links). They are one of the two node types in a behavior graph. You define behaviors using the behavior() factory method of an Extent.
*
* Behaviors have both static and dynamic links. You provide static links when you create the behavior. Behavior Graph will update dynamic links per special methods on BehaviorBuilder or you can update them directly on a behavior.
* @property extent A behavior always has an [Extent] with which it is created.
*/
class Behavior(
val extent: Extent, demands: List<Demandable>?, supplies: List<Resource>?,
internal var block: (Extent) -> Unit
) : Comparable<Behavior> {
class Behavior<T: Any>(
val extent: Extent<T>, demands: List<Demandable>?, supplies: List<Resource>?,
internal var thunk: ExtentThunk<T>
) : Comparable<Behavior<*>> {
/**
* The current set of all Resources which the behavior demands.
*/
Expand Down Expand Up @@ -42,7 +42,7 @@ class Behavior(
this.untrackedSupplies = supplies
}

override fun compareTo(other: Behavior): Int {
override fun compareTo(other: Behavior<*>): Int {
return order.compareTo(other.order)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
package com.yahoo.behaviorgraph
package behaviorgraph

fun interface DemandableLinks<T> {
fun invoke(ctx: T, demands: MutableList<Demandable?>)
}

fun interface SuppliableLinks<T> {
fun invoke(ctx: T, supplies: MutableList<Resource?>)
}

/**
* Provides a fluent API interface for creating a [Behavior].
Expand All @@ -14,16 +22,16 @@ package com.yahoo.behaviorgraph
* }
* ```
*/
class BehaviorBuilder<T: Extent>(
internal val extent: T
class BehaviorBuilder<T: Any>(
internal val extent: Extent<T>
) {
private var untrackedDemands: MutableList<Demandable> = mutableListOf()
private var untrackedSupplies: MutableList<Resource> = mutableListOf()
private var dynamicDemandSwitches: Array<out Demandable>? = null
private var dynamicDemandLinks: ((ext: T) -> List<Demandable?>?)? = null
private var dynamicDemandSwitches: List<Demandable>? = null
private var dynamicDemandLinks: DemandableLinks<T>? = null
private var dynamicDemandRelinkingOrder: RelinkingOrder = RelinkingOrder.RelinkingOrderPrior
private var dynamicSupplySwitches: Array<out Demandable>? = null
private var dynamicSupplyLinks: ((ext: T) -> List<Resource?>?)? = null
private var dynamicSupplySwitches: List<Demandable>? = null
private var dynamicSupplyLinks: SuppliableLinks<T>? = null
private var dynamicSupplyRelinkingOrder: RelinkingOrder = RelinkingOrder.RelinkingOrderPrior

/**
Expand All @@ -44,38 +52,75 @@ class BehaviorBuilder<T: Extent>(
* Example:
* ```kotlin
* extentInstance.behavior()
* .dynamicDemands(resource1, relinkingOrder = RelinkingOrder.RelinkingOrderSubsequent) { listOf(resource2, resource3) }
* .dynamicDemands(resource1, relinkingOrder = RelinkingOrder.RelinkingOrderSubsequent) { ctx, demands ->
* demands.add(resource2)
* demands.add(resource3)
* }
* .runs { ...
* ```
* @param switches When these resources change, the `links` code block will run to determine which additional demands a behavior should depend on.
* @param relinkingOrder Should the dynamic demands be set before or after the behavior is run. If in doubt choose `RelinkingOrderPrior`
* @param links This anonymous function should return the additional set of demands the behavior will include. The `ext` parameter points to the [Extent] this behaivor is created on.
* @param links This anonymous function passes in an empty list of dynamic demands. You should add any additional demands the behavior will include. The `ctx` parameter points to the context object for the [Extent] this behaivor is created on.
*/
fun dynamicDemands(vararg switches: Demandable, relinkingOrder: RelinkingOrder = RelinkingOrder.RelinkingOrderPrior, links: ((ext: T) -> List<Demandable?>?)) = apply {
@JvmOverloads
fun dynamicDemands(
switches: List<Demandable>,
relinkingOrder: RelinkingOrder = RelinkingOrder.RelinkingOrderPrior,
links: DemandableLinks<T>
) = apply {
dynamicDemandSwitches = switches
dynamicDemandLinks = links
dynamicDemandRelinkingOrder = relinkingOrder
}

@JvmOverloads
fun dynamicDemands(
vararg switches: Demandable,
relinkingOrder: RelinkingOrder = RelinkingOrder.RelinkingOrderPrior,
links: DemandableLinks<T>
) = apply {
dynamicDemandSwitches = switches.asList()
dynamicDemandLinks = links
dynamicDemandRelinkingOrder = relinkingOrder
}

/**
* Optional clause to include a set of supplies which can change based on other resources changing
*
* Example:
* ```kotlin
* extentInstance.behavior()
* .dynamicSupplies(resource1, relinkingOrder = RelinkingOrder.RelinkingOrderSubsequent) { listOf(resource2, resource3) }
* .dynamicSupplies(resource1, relinkingOrder = RelinkingOrder.RelinkingOrderSubsequent) { ctx, supplies ->
* supplies.add(resource2)
* supplies.add(resource3)
* }
* .runs { ...
* ```
*
* @param switches When these resources change, the `links` code block will run to determine which additional supplies a behavior should be responsible for.
* @param relinkingOrder Should the dynamic supplies be set before or after the behavior is run. If in doubt choose `RelinkingOrderPrior` (which is the default).
* @param links This anonymous function should return the additional set of supplies the behavior will include. The `ext` parameter points to the [Extent] this behaivor is created on.
* @param links This anonymous function passes in an empty list of dynamic supplies. You should add any additional supplies the behavior will include. The `ctx` parameter points to the context object for the [Extent] this behaivor is created on.
*/
fun dynamicSupplies(vararg switches: Demandable, relinkingOrder: RelinkingOrder = RelinkingOrder.RelinkingOrderPrior, links: ((ext: T) -> List<Resource?>?)) = apply {
@JvmOverloads
fun dynamicSupplies(
switches: List<Demandable>,
relinkingOrder: RelinkingOrder = RelinkingOrder.RelinkingOrderPrior,
links: SuppliableLinks<T>
) = apply {
dynamicSupplySwitches = switches
dynamicSupplyLinks = links
dynamicSupplyRelinkingOrder = relinkingOrder
}
@JvmOverloads
fun dynamicSupplies(
vararg switches: Demandable,
relinkingOrder: RelinkingOrder = RelinkingOrder.RelinkingOrderPrior,
links: SuppliableLinks<T>
) = apply {
dynamicSupplySwitches = switches.asList()
dynamicSupplyLinks = links
dynamicSupplyRelinkingOrder = relinkingOrder
}

/**
* Required clause which sets the block of code the behavior will run when one or more of its demands are updated.
Expand All @@ -84,7 +129,7 @@ class BehaviorBuilder<T: Extent>(
* @return The behavior that was created. Typically the results are discarded unless you need direct access to the
* behavior later.
*/
fun runs(block: (ext: T) -> Unit): Behavior {
fun runs(thunk: ExtentThunk<T>): Behavior<T> {
var dynamicDemandResource: Resource? = null
if (dynamicDemandSwitches != null) {
dynamicDemandResource = extent.resource("(BG Dynamic Demand Resource)")
Expand All @@ -105,7 +150,7 @@ class BehaviorBuilder<T: Extent>(
}
}

val mainBehavior = Behavior(extent, untrackedDemands, untrackedSupplies, block as (Extent) -> Unit)
val mainBehavior = Behavior(extent, untrackedDemands, untrackedSupplies, thunk)

if (dynamicDemandSwitches != null) {
var supplies: List<Resource>? = null
Expand All @@ -117,8 +162,9 @@ class BehaviorBuilder<T: Extent>(
demands.add(dynamicDemandResource!!)
}
Behavior(extent, demands, supplies) {
val demandLinks = dynamicDemandLinks!!(it as T)
mainBehavior.setDynamicDemands(demandLinks)
val mutableListOfDemands = mutableListOf<Demandable?>()
dynamicDemandLinks!!.invoke(it, mutableListOfDemands)
mainBehavior.setDynamicDemands(mutableListOfDemands)
}
}

Expand All @@ -132,8 +178,9 @@ class BehaviorBuilder<T: Extent>(
demands.add(dynamicSupplyResource!!)
}
Behavior(extent, demands, supplies) {
var supplyLinks = dynamicSupplyLinks!!(it as T)
mainBehavior.setDynamicSupplies(supplyLinks)
val mutableListOfSupplies = mutableListOf<Resource?>()
dynamicSupplyLinks!!.invoke(it, mutableListOfSupplies)
mainBehavior.setDynamicSupplies(mutableListOfSupplies)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//
// Copyright Yahoo 2021
//
package behaviorgraph

class BehaviorDependencyCycleDetectedException(s: String, val behavior: Behavior<*>, val cycle: List<Resource>) : BehaviorGraphException("$s Behavior=$behavior")

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// Copyright Yahoo 2021
//
package com.yahoo.behaviorgraph.exception
package behaviorgraph

open class BehaviorGraphException : RuntimeException {
constructor(message: String, ex: Exception?): super(message, ex)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package behaviorgraph

class ChildLifetimeCannotBeParent(val child: Extent<*>): BehaviorGraphException("Child lifetime cannot be a transitive parent.")
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.yahoo.behaviorgraph
package behaviorgraph

/**
* A Graph [Event] saves the current time when it runs.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.yahoo.behaviorgraph
package behaviorgraph

/**
* A behavior demands a set of **Demandables**.
Expand Down
Loading

0 comments on commit b1fe6df

Please sign in to comment.